레몬 생으로 씹어먹으면 맛있어요. :: init 메서드 구현에 관하여.

글쓴 사람 -페이스북 유저 - : https://www.facebook.com/sic.hhwang



[알고 계십니까]

우리가 흔히 사용하는 초기화 코드:
if((self=[super init])) {
// ... custom init codes here
}
return self;
는 절반만 옳은 코드입니다.

@ 설명
Apple 공식 Cocoa 디자인 패턴 문서에 따르면 super로의 init은 다음 네 가지 중 하나를 반환합니다:
1. self 그 자체. 다만 부모 클래스 부분만 초기화 완료 상태이기 때문에 추가적인 초기화가 필요합니다.
2. 같은 클래스의 다른 객체. 추가적인 초기화가 필요 없습니다. 예를 들자면, NSString의 클러스터 일원(cluster member) 객체가 있습니다. NSString은 사실 여러 개의 비밀(private) 섭클래스가 모여 만들어진 클래스 집합(class cluster)입니다. NSString은 초기화 과정에서 소속 클러스터의 일원(__NSCFString, __NSCFConstantString 등) 중 하나를 반환합니다.
3. 상수 객체. 예를 들면, [NSNumber numberWithInt:0]는 런타임 전체에 걸쳐 단 하나만 존재하는 Zero 객체를 반환합니다.
4. nil. 초기화에 실패한 경우입니다.

자, 이제 하나하나 따져볼까요?

1번의 경우는 사실 대부분의 코드에 해당됩니다. -[NSObject init]이 “return self”로 구현돼있기 때문에, NSObject를 상속한 태반의 객체가 바로 self를 받게 됩니다. 이 때, 몇몇 분이 이런 질문을 던지실 수 있습니다: “[super init]이 부모 클래스의 메서드를 호출하는 것이니, -[NSObject init]에서의 self는 NSObject형이 아니냐?” 답은 '아니요'입니다. [super init]은 objc_msgSendSuper(super, @selector(init))으로 치환됩니다. 여기서 super 인자는 objc/message.h에 선언된 struct objc_super로의 포인터이고, 이 구조체는 컴파일러가 필요한 때 생성합니다. struct objc_super의 맨 처음 필드가 id receiver인데, 이 receiver가 바로 섭클래스의 self입니다. 결국 이 objc_msgSendSuper()가 하는 일은 receiver, 즉 self의 메서드 리스트 중 부모 클래스가 물려준 메서드만 검색해 호출하는 겁니다. 따라서, -[NSObject init]에서의 self는 섭클래스의 self입니다.
2번의 경우가 바로 기존의 초기화 코드가 잘못 동작하는 예입니다. 아주 명확한 예제인 NSString을 가지고 설명하자면, “대부분의 경우”에 이미 init 또는 initWith… 계열 메서드에서, 조금 심한 경우에는 alloc에서 이미 초기화가 완료된 __NSCFString을 반환합니다. 이는 +[NSString string] 등의 convenient 메서드를 이용하는 경우에도 마찬가지입니다. 이 __NSCFString을 self에 대입한다고요? 그러면 self가 __NSCFString이 될 텐데, 얘는 우리가 섭클래스에서 추가한 인스턴스 변수라던가 프로퍼티를 가지고 있지 않아요. 그러면 이제 문제가 발생하는 겁니다. 운이 좋으면 unrecognized selector 예외, 아니면 segfault가 뜨겠죠. 심한 비약으로 들릴 수 있다는 건 압니다. 아무튼 문제가 발생할 요지가 있어요.
3번의 경우도 사실 2번과 동일합니다. 다만 얘는 init을 호출하기 이전에 이미 초기화가 완전히 끝난 “단 하나의 객체”가 나옵니다. 굳이 따지자면 싱글턴 객체예요.
4번의 경우는 기존 코드가 제대로 처리하는 것 중 하나입니다. [super init]의 결과가 nil이니 초기화 과정을 수행하지 않고 바로 nil을 반환하게 됩니다.

자, 그럼 이제 문제를 알았으니 제대로 된 코드는 어떻게 작성해야 하는지 알아볼까요? 답은 다음과 같습니다:
id initted=[super init];
if(initted==self) {
// … custom init codes here
}
return initted;

자, 위 네 가지 사례를 다시 한 번 대입해봅시다.
1번 사례. initted가 곧 self이니 self==self가 되어 if문 내부의 초기화 코드를 성공적으로 실행합니다.
2번 사례. initted!=self가 되니 추가 초기화를 수행하지 않고 바로 initted를 반환합니다.
3번 사례. 2번 사례와 동일합니다. 이미 그 자체로 완벽한 객체이니 바로 반환합니다.
4번 사례. nil이 나왔다면 이미 self!=nil이니 초기화가 이루어지지 않겠지요? nil이 바로 반환됩니다.

물론 기존의 초기화 코드로도 아무 문제가 없을 수 있습니다. 사실 이런 것은 별로 신경쓰지 않아도 되는 사소한 부분에 불과합니다. 저렇게 쓴다고 무조건 터지는 것도 아니고요. 요컨대 확률이 낮다 이겁니다. 이런 사소한 것에 세심한 주의를 기울이지 않아도 위대한 코드가 나올 수 있고 잘 작동할 수 있습니다.
정보 글로 시작해서 잡설에 가깝게 끝났지만, 요점은 이겁니다: 이왕이면 어떤 경우에도 '잘 터지지 않는' 코드를 만들어보자!

안전한 코드! 듣기 좋잖아요.
다 쓰고 나서 다시 두 번 읽어봤지만 야간 근무 도중에 쓴 거라 오류가 있을 수 있습니다. 오류는 댓글로 지적해주세요.
긴 글 읽어주셔서 감사합니다. :D

'아이폰' 카테고리의 다른 글

AppDelegate 접근방법  (0) 2013.02.20
iOS MVC패턴  (0) 2013.02.20
UITableViewCell을 상속하지 말지어다.  (0) 2013.02.18
iOS 기기 구분 하기.  (0) 2013.02.18
아이폰에 opencv 적용하기.  (1) 2012.11.16
Posted by 레몬사과
,