Swift && Objective-C에서 사용하는 메모리 관리 기법
객체에 대한 참조 카운트를 추적해서 사용되지 않는 메모리를 해제하는 메커니즘
직접 메모리 해제를 관리하지 않아도 돼서 메모리 관리 부담 감소
객체가 생성될 때 참조 카운트가 1로 시작되고 다른 객체나 변수에서 해당 객체를 참조할 때 참조 카운트가 증가
참조가 해제되거나 범위를 벗어나면 참조 카운트가 감소
참조 카운트가 0이 되는 시점에 ARC가 자동으로 메모리를 해제
→ 객체가 사용 중인지 아닌지 참조 수를 기반으로 판단해서 메모리를 자동으로 반환
컴파일러는 소스코드 파싱 후 Intermediate Representation인 SIL 생성 과정에서 ARC 관련 명령어를 넣음
retain : 참조 카운트 1 증가
release : 참조 카운트 1 감소
ex)
객체가 생성되는 시점에는 retain 명령어가 삽입돼서 참조 카운트 증가
객체에 대한 참조가 해제되거나 변수 스코프가 끝나면 release 명령어가 삽입돼서 카운트가 감소
SIL 명령어 레벨에서 ARC 코드가 자동 생성
%1 = function_ref @$s...ObjectACACycfC : ... // ObjectA() 생성, RC +1
ARC는 컴파일러가 자동으로 참조 관리 코드를 생성, 직접 메모리 할당/해제를 신경쓰지 않아도 됨
참조 카운트 증가/감소만 자동 관리하고, 참조 간의 순환 구조를 탐지하거나 해제하는 건 못함
두 객체가 강한 참조로 갖고 있을 때, 참조 카운트가 0으로 감소하지 않아 메모리 해제가 불가능해짐
ex) 클래스 인스턴스 간의 강한 참조 || 클로저가 self를 강한 참조로 캡처하는 경우
메모리 누수를 완전히 막지는 못해서 참조 타입 관리가 중요
참조 카운트 증가 여부를 결정하는 참조 타입
| 참조 타입 | 참조 카운트 증감 | 옵셔널 여부 | 해제된 메모리 접근 시 행동 |
|---|---|---|---|
| Strong | +1 (카운트 증가) | 아님 | 메모리 해제 방지 |
| Weak | 카운트 증가 안함 | 옵셔널(Optional) 타입 | 해제되면 nil로 자동 설정, 안전한 접근 가능 |
| Unowned | 카운트 증가 안함 | 옵셔널 아님 | 해제된 객체 접근 시 런타임 크래시 발생 |
Strong Reference : 기본 참조 타입. 객체의 생명주기 연장
Weak Reference : 메모리 누수 방지를 위해 참조 카운트를 증가시키지 않고
객체가 해제되면 자동으로 nil로 바뀜 (안정성 보장!), 옵셔널 타입
Unowned Reference : 참조 카운트를 증가시키지 않지만 옵셔널이 아니고
객체가 해제된 뒤에 접근하면 런타임 에러가 발생함 weak보다 빠르지만 안전성이 떨어짐
객체의 참조 카운트는 객체 내부에 저장되지만 Weak 참조를 위한 추가 메타데이터는 SideTable에 관리됨
클래스는 기본적으로 즉시 해제 방식으로 ARC 수행
Objective-C 런타임 객체, autorelease 메시지가 호출되면 Autorelease Pool에 등록돼서 release가 지연됨
Autorelease Pool이 해제될 때까지 객체의 참조 카운트 감소를 미뤄두었다가 일괄 처리
순환 참조로 인해 참조 카운트가 0이 되지 않는 경우
Closure가 self 또는 다른 객체를 강한 참조로 캡처하는 경우
ARC 관리 밖 객체를 생성/사용 후 해제하지 않는 경우
Xcode Instruments의 Leak Instrument로 실행 중 누수 탐지
Memory Graph Debugger로 앱 실행 시점 객체 참조 관계 시각화
클래스 deinit에 로그를 추가해 예상대로 메모리가 해제되는지 확인