본문 바로가기

Dev.iOS/Objective-C

[Objective-C] 5. 메모리 관리와 ARC에 대해서

Chapter 5. 메모리 관리와 ARC


Objective-C에서 개발자가 사용 가능한 메모리 관리 모델은 세 가지가 존재한다.
1. 자동 가비지 컬렉션

2. 수동 레퍼런스 카운팅 및 오토릴리즈 풀
3. 자동 레퍼런스 카운팅(ARC)


1. 자동 가비지 컬렉션
가비지 컬렉션을 사용하면, 시스템이 자동으로 어떤 객체가 어떤 객체를 소유하는지에 대한 정보를 지속적으로 파악하고, 프로그램이 실행되는 동안 메모리 공간이 필요하면 더는 참조되지 않는 객체들을 자동으로 메모리에서 해제한다.
하지만 iOS 런타임 환경에서는 가비지 컬렉션이 지원되지 않는다. Mac OS 프로그램을 개발할 때만 가비지 컬렉션을 사용할 수 있다.


2. 수동 레퍼런스 카운팅 및 오토릴리즈 풀
수동 레퍼런스 카운팅은 기존의 코드를 ARC로 이전할 수 없는 경우에 사용된다. 기본 개념을 알아보자. 객체가 생성되면 초기 레퍼런스 카운트가 1로 설정된다. 매번 객체가 지속되도록 해야 할 때 마다 레퍼런스 카운트를 1씩 증가시켜 참조를 생성하게 된다. 이 작업은 retain 메시지를 객체에 보내 수행한다. 반대로 더 이상 객체가 필요하지 않으면, release 메시지를 보내 레퍼런스 카운트를 1씩 줄여준다.

이런 과정을 반복하다가 객체의 레퍼런스 카운트가 0이 되면 해당 객체가 더 이상 사용되지 않음을 시스템이 알 수 있다. 그래서 해당 객체가 차지하고 있던 메모리 공간을 해제한다. 이 과정은 객체에 dealloc 메시지를 보내 처리한다. 객체가 메모리에서 해제된 뒤에 그 객체를 참조하는 것을 dangling pointer(길 잃은 포인터)라고 한다. 이 dangling pointer에게 메시지를 보내면 애플리케이션이 강제로 종료될 수가 있으므로 주의해야 한다.

이와 같은 이유로 수동 레퍼런스 카운팅을 할 때 지켜야하는 몇 가지 규칙이 존재한다.
- 다른 이에 의해 제거되지 않아야 할 객체는 retain하며 객체 사용이 끝났다면 release해줘야 한다.
- 객체에 release 메시지를 보낸다고 객체가 꼭 파괴되는 것은 아니다. 객체의 레퍼런스 카운팅 값이 0이 될 때 객체는 파괴된다.
- 직접 retain한 객체는 직접 release해줘야 한다.


3. ARC(Auto Reference Counting)
ARC, 자동 레퍼런스 카운팅은 수동 레퍼런스 카운팅의 잠재적은 문제점을 모두 제거해준다. 내부적으로는 레퍼런스 카운트를 여전히 관리하고 추적한다. 하지만 시스템이 객체를 언제 리테인하고  오토릴리스하고 릴리즈할지 결정하기 때문에 개발자 입장에서는 더이상 신경쓰지 않아도 되는 것이다. 그렇다. retain / release로 코드 복잡도가 증가하여 Auto Reference Counting 이 도입되었다. 말 그대로 컴파일러가 retain/release를 통해 reference counting을 관리해주는 것이다.

ARC는 GC와는 다르게 런타임이 아닌 컴파일 단에서 처리된다. GC는 런타임에 메모리를 검사하기 때문에 애플리케이션 퍼포먼스에 악영향을 주게 된다. 그러나 ARC는 컴파일러가 retain / release를 컴파일러가 코드에 삽입하는 것이므로 성능 저하를 유발하지 않는다. ARC가 도입되면서 더이상 retain / release / autorelease를 직접 삽입할 필요가 없어졌다. 컴파일러가 다 알아서 해준다. dealloc 메소드는 사용할 수는 있지만 이는 인스턴스 변수들의 메모리 해제(release)가 아닌 자원 관리 차원에서만 허용된다. 재정의된 dealloc 메소드에서는 [super dealloc]을 호출해서는 안된다.

한 가지 덧붙이자면, ARC는 Object만을 고려한다. 구조체 내의 Object는 ARC가 계산하기 어렵다. 구조체가 아닌 Object-C Class를 사용하도록 하자.

ARC는 객체의 수명을 지정하는 몇 가지 새로운 lifetime qualifier와  zeroing weak reference를 제공한다. 약한 참조(weak)는 대상 객체의 수명에 영향을 주지 않지만 zeroing weak는 대상 객체가 해제되면 자동으로 nil 값을 갖게 된다. ARC는 이전 참조카운팅 방식(MRR or MRC)에서 발생하던 strong reference cycles(retain cycle) 문제를 방지해주지 않는다.

lifetime qualifier
__strong : default value
__weak : zeroing weak refercene. 대상 객체가 해제되면 nil이 된다.
__unsafe_unretained : weak reference. 대상 객체가 해제되면 댕글링 포인터가 된다.
__autoreleasing : id* 형식의 인수가 리턴시에 자동적으로 해제되도록 지정해준다.

Strong
기본적으로 모든 객체 포인터 변수는 strong 변수이다. 변수에 객체를 대입하면 자동으로 retain한다는 말이다. 게다가 참조한 이전 객체는 대입 이전에 release된다. strong 변수는 기본적으로 인스턴스 변수든, 로컬 변수든, 글로벌 변수든 0으로 초기화된다. 참고로 프로퍼티의 default 값은 strong이 아니다. 프로퍼티의 기본 속성은 unsafe_unretained이다.

Weak
weak 변수를 선언하면 몇 가지 일이 일어난다. 시스템은 해당 변수에 대입된 레퍼런스를 추적한다. 이 참조된 객체가 할당 해제되면 weak변수는 자동으로 nil로 설정된다. 이를 통해 이 변수에 실수로 메시지를 보냈을 때 발생할 수 있는 강제종료를 막을 수 있다.
 

ARC에 대한 더 자세한 내용은 아래 링크를 참조하면 된다.



chapter 5. end