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

[Effective Objective-C] #31 참조를 릴리스하고 관찰 상태(observation state)를 정리하는 일은 dealloc 메서드에서만 하라

by 돼지왕 왕돼지 2017. 9. 24.
반응형

 [Effective Objective-C] #31 참조를 릴리스하고 관찰 상태(observation state)를 정리하는 일은 dealloc 메서드에서만 하라


출처 : Effective Objective-C

.cxx_destruct, applicationWillTerminate, arc, arc super dealloc, c api, CFRelease, CLOSE, corefoundation, dealloc, dealloc 직접 호출, File Descriptor, IOS, nsapplicationdelegate, nsnotifcationcenter dealloc, NSNotification, nsnotificationcenter, observation behavior, observation state, Observer, OSX, removeobserver, retain count, super dealloc, UIApplication, UIApplicationDelegate, winding down, [Effective Objective-C] #31 참조를 릴리스하고 관찰 상태(observation state)를 정리하는 일은 dealloc 메서드에서만 하라, 관찰 상태 정리, 관찰 행동, 관찰자, 데이터베이스 연결, 런타임, 로그, 리테인, 리테인 수, 메모리 블록, 불일치, 비동기, 비동기 작업, 비오브젝티브-c, 소켓, 소켓 관리 객체, 수동 참조 세기, 스레드 전환, 앱 델리게이트, 예외, 자동 수행, 재귀 호출, 재정의, 종료 상태, 참조 릴리스, 참조 릴리즈, 크래시, 키-값 관찰자, 특정 스레드, 파일 디스크립터, 프로그래머 에러, 프로퍼티 접근자


-

생애 주기가 끝난 객체는 할당 해제되고 dealloc 메서드가 호출될 것이다.

이 메서드는 객체의 생애 주기 동안 리테인 수가 0으로 떨어질 때 딱 한 번 호출된다.

호출되는 시점은 보장되지 않는다.


그렇지만 리테인과 릴리스가 있는 코드를 살펴보고 언제 호출될지 예상할 수 있다고 생각할 수도 있다.

그러나 실제로는 여러분이 모르는 사이에 어떠한 라이브러리라도 객체를 조작할 수 있다.

이는 예정되지 않는 시점에 할당 해제가 일어나게 하는 원인이 될 수 있다.



-

절대로 dealloc 을 직접 호출하면 안 된다.

런타임이 필요할 때에 그것을 호출할 것이다.

dealloc 이 호출된 객체는 더는 유효하지 않기 때문에 그 객체에 대한 이후의 메서드 호출은 유효하지 않다.



-

그러면 dealloc 내에서 무엇을 해야 하는가?

중점을 두어 해야 할 일은 객체가 소유한 참조들을 릴리스하는 것이다.

이는 오브젝티브-C 객체와 ARC 가 .cxx_destruct 자동 메서드를 통해 자동으로 dealloc 메서드에 추가한 것들을 릴리즈함을 뜻한다.



-

객체가 소유한 비오브젝티브-C 객체들도 릴리스해야 한다.

예를 들어 CoreFoundation 객체는 명시적으로 릴리스해야 한다.

이것은 순수 C API 이기 때문이다.



-

dealloc 메서드 내에서 흔히 하는 또 한 가지 일은 설정된 관찰 행동(observation behavior)을 제거하는 일이다.

특정 알림을 위한 객체를 등록하려고 NSNotificationCenter 를 사용했다면 dealloc 은 알림을 해제하기 좋은 장소다.

그래서 앱이 크래시 원인이 될 수 있는 할당 해제된 객체에 메시지를 보내는 일이 없게 해준다.


dealloc 메서드는 다음과 같다.

- (void)dealloc {

     CFRelease(coreFoundationObject);

     [[NSNotificationCenter defaultCenter] removeObserver:self];

}



-

ARC 가 아닌 수동 참조 세기를 사용하면 마지막에 [super dealloc] 을 호출해야 하지만

ARC 는 이를 자동으로 수행해준다.

이는 ARC 를 사용하는 것이 수동 참조 세기를 사용하는 것보다 훨씬 쉽고 안전하다는 것을 보여주는 또 다른 이유다.

수동 참조 세기를 사용하면 이 메서드에서 객체가 소유한 모든 오브젝티브-C 객체를 수동으로 릴리스해야 한다.



-

시스템에서 잠재적으로 비싸고 부족한 리소스를 해제하지 말아야 한다.

그런 리소스에는 파일 디스크립터(file descriptor), 소켓, 많은 양의 메모리 블록 같은 것이 있다.

dealloc 메서드가 정해진 시간에 호출된다고 가정하면 안 된다.

여러분이 모르는 어떤 것이 그 객체를 잡고 있을 수 있기 때문이다.

그런 경우 그렇지 않아도 부족한 시스템 리소스를 필요보다 더 오랫동안 보유하고 있게 된다.

이는 원하는 상황이 아니다.

이러한 상황에서 대안은 앱이 객체 사용이 끝났을 때 호출되는 또 다른 메서드를 구현하는 것이다.

리소스의 생명 주기는 이로써 좀 더 예측 가능하게 된다.



-

정리 메서드가 필요한 객체의 예는 서버에서 소켓을 관리하는 객체, 데이터베이스 연결 등일 것이다.

그런 클래스의 인터페이스는 다음과 같다.

@interface EOCServerConnection : NSObject

- (void)open:(NSString*)address;

- (void)close;

@end


이 클래스는 연결을 맺기 위해 open: 메서드를 호출하는 것으로 설계됐다.

연결 사용이 끝나면 앱은 close 를 호출한다.

close 는 연결 객체가 할당 해제되기 전에 호출되어야 한다.

그렇게 하지 못하면 이는 프로그래머 에러로 여겨진다.

이 실수는 reference count 에서 리테인과 릴리스 관리를 잘못해 균형이 깨지는 것과 같다.



-

dealloc 이 아닌 다른 정리 메서드에서 리소스를 정리하는 또 다른 이유는 사실 dealloc 메서드는 생성된 모든 객체에서 실행된다는 보장을 할 수 없기 때문이다.

이는 앱이 종료되었는데도 여전히 객체가 살아 있는 드문 경우다.

이러한 객체는 dealloc 메시지를 받지 않는다.

대신 그 객체들은 앱이 종료되어 앱이 사용한 리소스를 운영 체제에 반환할 때 dealloc  호출 없이 그냥 제거가 된다.

이는 dealloc 메서드를 호출하지 않는 최적화다.

그리고 모든 객체에 대해 호출된다는 보장은 할 수 없음을 의미한다.



-

맥 OS X 와 iOS 앱은 둘 다 앱 델리게이트 내에 앱이 종료될 때 호출되는 메서드를 가지고 있다.

이 메서드는 꼭 정리될 필요가 있는 객체들을 정리하는 정리 메서드를 호출하는 데 사용될 수 있다.


맥 OS X 의 경우 앱이 종료될 때 호출되는 메서드가 있는 프로토콜은 NSApplicationDelegate 다.

- (void)applicationWillTerminate:(NSnotification *)notification


iOS 의 경우 메서드가 있는 프로토콜은 UIApplicationDelegate 다.

- (void)applicationWillTerminate:(UIApplication *)application



-

리소스를 관리하는 객체를 위한 정리 메서드는 dealloc 메서드 내에서 호출되어야 한다.

정리 메서드가 호출되지 않는 가능성을 줄이기 위해서다.

호출되지 않으면 프로그래머 에러(즉 개발자가 꼭 써야 하는 코드를 작성하지 않아 생기는 에러)가 생긴 걸 알리기 위해 로그를 남기는 것이 좋은 생각이다.

그리고 호출되지 않은 것은 프로그래머 에러다.

close 는 객체가 할당 해제되기 전에 호출되어야 하기 때문이다.

호출되지 않으면 close 메서드는 있으나마나다.

이 로그는 문제를 해결하라고 프로그래머에게 경고를 줄 것이다.

누수를 피하기 위해 dealloc 내에서 자원을 닫는 것은 여전히 좋은 방법이다.

close 와 dealloc 메서드의 예는 다음과 같다.


- (void)close{

     /* 리소스를 정리한다. */

     _closed = YES;

}


- (void)dealloc {

     if ( !_closed ){

          NSLog(@“ERROR: close was not called before dealloc!”);

          [self close];

     }

}


close 메서드가 호출되지 않았을 때 단순히 에러를 로깅하는 대신, 심각한 프로그래머 에러가 발생한 것을 알리기 위해 예외를 던지게 할 수도 있다.



-

할당해제되는 객체가 종료되고 있는 상태(winding-down)에서

다른 메서드가 비동기로 동작하거나 재귀 호출이면 할당 해제된 객체는 그 메서드가 작업이 끝난 후에야 완전히 죽을 수 있다.

이는 온갖 종류의 문제 원인이 될 수 있고 가끔 앱의 크래시 원인이 된다.

종료된 객체에 재호출을 할 수 있기 때문이다.

객체가 이미 죽었으면 그 호출은 실패할 것이다.



-

dealloc 메서드는 스레드에서 마지막 릴리스 호출로 리테인 수가 0이 될 때 호출된다.

몇몇 메서드는 반드시 메인 스레드 같은 특정 스레드에서 실행되어야 한다.

이러한 메서드가 dealloc  에서 호출되면 올바른 스레드에서 실행되는 것이 보장되지 않는다.


강제적으로 올바른 스레드에서 실행되게 하는 코드는 전혀 안전하지 않다.

객체가 할당 해제되고 있는 상태이고 런타임은 벌써 이 상태를 표시하기 위해 내부 데이터 구조를 변경하기 시작했기 때문이다.



-

dealloc 메서드 내에서 호출하는 것을 피해야 할 또 다른 메서드는 프로퍼티 접근자들이다.

프로퍼티 접근자는 재정의될 수 있고, 또 그 재정의된 구현 내용이 객체가 할당 해제하는 동안 실행되면 안전하지 않을 수도 있기 때문이다.

프로퍼티는 키-값 관찰자를 통해 관찰할 수도 있다.

그리고 관찰자(observer)는 할당 해제되는 객체를 사용하여 그 객체를 리테인하는 것 같은 몇 가지 일을 하려고 할 수도 있다.

그렇게 함으로써 런타임이 완전한 불일치 상태가 될 수 있다.

그래서 이상한 크래시를 야기할 수 있다.




기억할 점


dealloc 메서드는 오직 다른 객체의 참조를 릴리스하고 키-값 관찰자나 NSNotificationCenter 알림 같은 것을 등록 해제하는 데만 사용되어야 한다.


객체가 파일 디스크립터 같은 시스템 자원을 잡고 있으면

이러한 리소스를 반납하기 위한 메서드가 있어야 한다.

이러한 자원의 사용이 끝났을 때 클래스의 사용자가 close 메서드를 반드시 호출하게 해야 한다.


dealloc 메서드 내에서 비동기 작업을 하는 메서드나 절대로 될 수 없는 상태로 변하는 것을 가정하는 메서드는 호출하지 말아야 한다.




반응형

댓글