본문 바로가기
프로그래밍 놀이터/iOS

[iOS Study] ARC 를 통한 메모리 관리

by 돼지왕 왕돼지 2016. 2. 15.
반응형


 [iOS Study] ARC 를 통한 메모리 관리


출처 : 아론 힐리가스의 iOS 프로그래밍

@autoreleasepool, @property, @synthesize, accessor, arc, Atomic, automatic reference counting, autorelease, autorelease pool, clang, COPY, dangling pointer, FRAME, getter, heap, immutable, IOS, ios study, ios tutorial, manual reference counting, nil, nonatomic, NSArray, NSMutableArray, NSMutableString, NSString, Objective-C, premature deallocation, property, readonly, reatin, release, retain, retain cycle, Setter, Stack, strong, strong reference, synthesize, unsafe_unretained, weadwrite, weak, weak reference, xcode, [iOS Study] ARC 를 통한 메모리 관리, _, __weak, 강한 참조, 강한 참조 순환, 객체, 공헌, 기본 개념, 기본값, 누수, 다른 객체, 댕글링 포인터, 리테인 사이클, 멀티스레딩, 메모리 공간, 메모리 관리, 메모리 누수, 메모리 속성, 명시적, 명시적 합성, 배열, 변경 가능한, 변경 불가능한, 변수, 복사, 부모, 부모 자식, 불변, 비객체, 사본, 소멸, 소유권, 소유자, 수동 레퍼런스 카운팅, 스택, 암시적 합성, 약한 참조, 언더바, 오토릴리즈, 오토릴리즈 풀, 오픈 소스, 인스턴스 변수, 자동 레퍼런스 카운팅, 자식, 접근자, 정적 분석기, 조기반환, 조부모, 지시자, 최적화, 컬렉션, 프레임, 하위 클래스, 합성, 호출자



-
메소드나 함수가 실행되면 스택(stack)이라고 하는 메모리 영역의 일부가 할당된다.
이 할당된 메모리 공간을 프레임(frame)이라고 한다.


-
프로그램을 시작하고 main() 이 실행되면 main() 의 프레임이 스택에 들어간다.
main() 이 다른 메소드(또는 함수)를 호춣하면 그 메소드의 프레임은 스택의 꼭대기에 들어간다.

각 메소드나 함수가 끝날 때마다 해당 프레임은 스택 꼭대기에서 즉시 빠져나와 사라진다.
그 메소드가 다시 호출되면 새 프레임이 할당되고 스택에 들어간다.


-
ARC(
자동 레퍼런스 카운팅, Automatic Reference Counting) 가 프로그램의 메모리 관리를 대신해준다.


-
여전히 필요한 객체를 소멸하는 것을 조기반환(premature deallocation) 이라고 한다.


-
객체가 소유자를 잃는 경우는 다음과 같다.

객체를 가리키는 변수가 다른 객체를 가리키도록 변경됐다.
객체를 가리키는 변수를 nil 로 설정했다.
객체의 소유자 그 자체가 소멸됐다.
배열과 같은 컬렉션에 저장된 객체가 컬렉션에서 제거됐다.


-
포인터 변수가 가리키는 객체는 소유자를 가지고 계속 살아있을 수 있다.
이를 강한 참조(strong reference) 라 한다.

객체의 소유권을 얻지 못하는 변수를 약한 참조(weak reference) 라 한다.

강한 참조 순환은 둘 이상의 객체가 서로 강한 참조를 가지고 있을 때 발생한다.
두 객체가 서로 소유할 때 그 객체들은 ARC 에 의해 절대 소멸될 수 없다.

이를 해결하려면 포인터 중 하나를 약한 참조로 만들어야 한다.
어느 것을 약한 참조로 만들 것인지 결정하려면 이 순환에서 객체들을 부모-자식 관계로 여겨야 한다.
이 관계에서 부모는 자식을 소유할 수 있지만 자식은 절대 부모를 소유해서는 안 된다.


-
약한 참조로 변수를 선언하려면 __weak 속성을 사용한다.


-
대부분 강한 참조 순환은 부모-자식 관계로 대체할 수 있다. 부모는 보통 자식의 강한 참조를 유지하고 만약 자식이 부모의 포인터가 필요하면 그 포인터는 약한 참조여야 강한 참조순환을 피할 수 있다.

자식이 조부모에 대한 강한 참조를 가지고 있는 경우에도 강한 참조 순환이 발생하게 된다
따라서 이 상황에서도 동일한 규칙을 적용한다.


-
애플의 개발 도구에는 강한 참조 순환을 찾도록 도와주는 Leaks 도구가 포함되어 있다.


-
약한 참조는 해당 포인터가 가리키는 객체가 언제 소멸되는지를 알고 자신을 nil 설정한다.


-
@property (options) (type) (varName)

프로퍼티 선언은 기본적으로 세 가지를 만든다.
인스턴스 변수와 그 변수에 대한 두 접근자( getter / setter )이다.


-
프로퍼티명은 인스턴스 변수명에서 언더바(_) 를 뺀 것이다.
프로퍼티에 의해 만들어진 인스턴스 변수는 이름에 언더바를 포함한다.

itemName 이라는 이름의 프로퍼티를 선언하면, _itemName 이라는 이름의 인스턴스 변수를 갖고,
Getter 메소드는 itemName, Setter 메소드는 setItemName: 이라는 이름을 갖는다.


-
프로퍼티의 멀티스레딩 속성은 nonatomic 과 atomic 의 두 가지 값을 가진다.
대다수 iOS 프로그래머들은 보통 nonatomic 을 사용한다.
이 속성의 기본값은 atomic 이다.
따라서 atomic 으로 하기 싫다면 프로퍼티에 직접 nonatomic 을 명시해야 한다.







-
읽기/쓰기 속성에 가능한 값은 readWrite 와 readOnly 이다.
컴파일러에 프로퍼티의 세터 메소드를 구현할지 말지를 알려준다.
기본 옵션은 readWrite 이다.


-
메모리 관리 속성값에는 strong, weak, copy, unsafe_unretained 가 있다.
객체를 가리키지 않는 프로퍼티는 메모리를 관리할 필요 없어 unsafe_unretained 옵션을 사용하면 된다.
직접 할당을 의미한다.

약한 참조와 달리 unsafe_unretained 참조는 가리키는 객체가 소멸될 때 자동으로 nil 을 설정하지 않는다.
이것은 댕글링 포인터가 되기 때문에 위험하다.

unsafe_unretained 는 비객체 프로퍼티의 기본값이며 유일한 옵션이다.


-
포인터를 관리하는 옵션의 기본값은 strong 이다.
Objective-C 프로그래머들은 명시적으로 이 속성을 선언하는 경향이 있다. 
( 그 이유는 지난 몇 년간 기본값이 변경됐고 앞으로도 그럴 가능성이 높기 때문이다. )


-
어떤 클래스가 변경 가능한 하위 클래스를 가진다면( 예 : NSString/NSMutableString, NSArray/NSMutableArray ) 그 타입을 가리키는 프로퍼티는 메모리 속성을 copy 로 설정해야 한다.

NSString 의 copy 메소드는 원래 문자열과 동일한 값을 가진 변경 불가능한 NSString 객체를 반환한다.



-
소유권 개념에서 copy 는 가리키는 객체에 대한 강한 참조이다.
원래 문자열은 어떤 방법으로도 수정되지 않는다.
소유자를 얻거나 잃지 않고 데이터의 변경도 이루어지지 않는다.

copy 는 변경 가능한 객체를 복사하는 좋은 방법이기는 하지만,
변경 불가능한 객체의 사본을 만드는 불필요한 행위를 한다.
변경 불가능한 객체는 수정할 수 없으며 따라서 위에서 설명한 종류의 문제는 발생하지 않는다.

불필요한 복사를 막기 위해 변경 불가능한 클래스는 copy 메소드를 원본 객체의 포인터를 그대로 반환하도록 구현한다.


-
사용자 정의 세터와 게터를 둘 다 구현하면(또는 read-only 프로퍼티에서 사용자 정의 게터를 정의한 경우),
컴파일러는 프로퍼티를 위한 인스턴스 변수를 만들지 않는다는 것을 명심해야 한다.
만약 인스턴스 변수가 필요하다면 직접 선언해야 한다.



-
프로퍼티를 선언하는 것은 단지 클래스 인터페이스 안에 접근자 메소드를 선언할 뿐이다.
프로퍼티가 자동으로 인스턴스 변수를 만들고 접근자를 구현하기 위해서는 명시적으로나 암시적으로 반드시 합성해야 한다.(Synthesize).
기본적으로 프로퍼티는 암시적으로 합성된다.
프로퍼티를 명시적으로 합성하려면 구현 파일에서 @synthesize 지시자를 사용해야 한다.


-
@synthesize age = _age;

첫 번째 속성(age)은 age 와 setAge: 메소드를 생성하라고 지시하고,
두 번째 속성(_age)은 이들 메소드의 인스턴스 변수는 _age 가 돼야 한다고 지시한다.


-
@synthesize age;
// 위와 동일한 코드
// @synthesize age = age;


-
수동 레퍼런스 카운팅(Manual reference counting)에서는

포인터를 다른 것을 가리키도록 설정하기 전에 객체에 release 메시지를 보내는 것을 잊어버리면 메모리 누수가 발생할 것이다.
이전에 객체에 retain 을 보내지 않은 상태에서 객체에 release 를 보내면 조기 메모리 해제가 된다.

Clang 정적 분석기로 알려진 오픈소스 프로젝트에 많은 공헌을 하고, 결국 그것을 Xcode 에 통합했다.
정적 분석기의 기본 개념은 코드를 분석하여 잘못된 곳을 찾을 수 있다는 것이다.

애플은 정적 분석기가 꽤 훌륭하다는 것을 깨닫고, 정적 분석기가 retain 과 release 메시지를 넣어 주기를 원했다.
그래서 ARC 가 탄생한 것이다.


-
수동 레퍼런스 카운팅 시절의 프로그래머들이 이해해야 할 또 다른 것은 오토릴리즈 풀(autorelease pool) 이다.
객체가 autorelease 메시지를 받으면 오토릴리즈 풀은 객체의 소유권을 임시로 얻게 된다.
객체의 생성자나 수신자가 소유권에 대한 책임 없이 객체를 생성하고 반환할 수 있다.


-
소유권을 포기하기 위해서는 객체에 release 메시지를 보내야 하기 때문에
이 메소드의 호출자는 소유권을 책임져야 한다.
하지만 소유권은 혼동하기 쉽다.

따라서 alloc 과 copy 외의 메소드로 만들어지는 객체들은 반환되기 전에 autorelease 메시지를 받고 객체의 리시버가 필요에 따라 소유권을 얻거나 오토릴리즈 풀에서 사라질 때 소멸될 것이다.

이것은 ARC 를 통해 자동으로 이루어진다. ( 그리고 종종 완전히 최적화된다. )

오토릴리즈 풀은 @autoreleasepool 지시자와 중괄호 { } 로 만들어진다.
중괄호 내부에서 이름에 alloc 이나 copy 를 포함하지 않은 메소드가 반환한 새로 초기화된 객체는 오토릴리즈 풀에 위치하게 된다.
중괄호가 닫힐 때 오토릴리즈 풀의 객체는 소유자를 잃는다.


-
iOS 앱은 자동으로 오토릴리즈 풀을 만들어주므로 우리가 직접 관여할 필요가 없다.









반응형

댓글