[iPhone 개발 Tip #2] delegate 이렇게 쓰면 큰일난다!
옆의 스크린샷과 같이
어떤 ParentViewController가 ChildViewController의 View를 포함하고 있고, ChildViewController의 Change Color라는 버튼을 누르면 ParentViewController의 Background Color를 변경하는 경우가 있다고 가정해봅시다.
간단히 샘플을 예로 든 것이지만 정리하자면
ChildViewController 가 ParentViewController 를 delegate로 가지고 있는 디자인 패턴입니다.
RootViewController가 있고 ParentViewController를 동적으로 할당하고 해제합니다.
// RootViewController -(IBAction) doAlloc{ if(viewController == nil){ viewController = [[ParentViewController alloc] initWithNibName:@"ParentViewController" bundle:nil]; [self.view addSubview:viewController.view]; } } -(IBAction) doRelease{ if(viewController.view.superview != nil) [viewController.view removeFromSuperview]; [viewController release]; viewController = nil; }
그리고 중요한 부분의 코드를 표현하자면 아래와 같습니다.
먼저 이것은 잘못된 예로 ChildViewController에서 delegate property를 retain 속성으로 선언한 형태입니다.
// some code in ChildViewController.h @interface ChildViewController : UIViewController { UIViewController * delegate; } -(IBAction) changeParentBackground; @property (retain, nonatomic) UIViewController * delegate; @end // some code in ChildViewController.m - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"ChildViewController viewDidLoad"); } -(IBAction) changeParentBackground{ delegate.view.backgroundColor = [UIColor greenColor]; } - (void)dealloc { NSLog(@"ChildViewController dealloc"); [delegate release]; [super dealloc]; }
ParentViewController 에서 ChildViewController를 초가화할 때
delegate에 ParentViewController의 인스턴스 즉, self를 넘겨주는 부분입니다.
// some code in ParentViewController.m - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"ParentViewController viewDidLoad"); childViewController = [[ChildViewController alloc] initWithNibName:@"ChildViewController" bundle:nil]; childViewController.view.frame = CGRectMake(50, 100, 200, 100); childViewController.delegate = self; [self.view addSubview:childViewController.view]; } - (void)dealloc { NSLog(@"ParentViewController dealloc"); [childViewController release]; [super dealloc]; }
위의 코드는 보기에는 별 이상이 없이 보입니다.
ChildViewController의 dealloc 함수에서 retain한 delegate를 release 해주었기 때문이지요.
그러나 상당히 불안한 코드로서 이렇게 사용하시면 절대 안됩니다.
Alloc, Release 버튼을 통하여 NSLog로 출력한 내용을 콘솔로 살펴보겠습니다.
2010-05-27 14:59:20.762 DelegateTest[1488:207] doAlloc 2010-05-27 14:59:20.764 DelegateTest[1488:207] ParentViewController viewDidLoad 2010-05-27 14:59:20.765 DelegateTest[1488:207] ChildViewController viewDidLoad 2010-05-27 14:59:21.883 DelegateTest[1488:207] doRelease
위의 로그를 보시는 것 과와 같이 doRelease 부분에서 ParentViewController의 인스턴스 viewController를 release 했음에도 불구하고 실제 ParentViewController의 dealloc 함수는 불리어지지 않았습니다.
왜 그럴까요?
이유는 ChildViewController의 delegate Property 속성이 retain이기 때문입니다
childViewController = [[ChildViewController alloc] initWithNibName:@"ChildViewController" bundle:nil];
childViewController.view.frame = CGRectMake(50, 100, 200, 100); childViewController.delegate = self; //check this
check this 부분을 보시면 delegate 프로퍼티에 retain 하기 때문에 저 부분을 지나고 나면 ParentViewController의 retainCount는 2가 됩니다. 그래서 ParentViewController의 인스턴스를 가지고 있는 RootViewController에서 viewController를 release하면 그 당시에는 retainCount가 2이기 때문에 단지 retainCount를 1로 감소시키는 것 이외에 다른 현상이 생기지 않습니다.
그래서 ParentViewController의 dealloc 함수가 호출되지 않는 것입니다.
또한 당연히 그 이유로 ChildViewController의 인스턴스는 leak으로 남는 것 입니다.
iPhone 개발 시에 많은 오해를 하시는 부분이 있는데Instruments의 Leaks 툴은 상당수의 leak을 잡아내지 못하는 경우가 있습니다.
이번과 같은 문제도 leak이라고 판단하지 못하는 경우입니다.
그러나 Instruments의 Activity Monitor를 통하여 실제 메모리가 어떻게 변하는 지 살펴보겠습니다.
제일 처음 파란색으로 보는 부분인 doAlloc을 한번도 하지 않은 상태구요.
두번째는 doAlloc, doRelease를 한번씩 실행하였고
세번째는 다시 doAlloc, doRelease를 실행한 상태입니다.
네번째는 또 한번더 실행.
아이폰에서 저 정도의 leak이 하나의 어플리케이션에서 계속 나다보면 결국에는 아이폰 화면이 꺼지면서 자동으로 shutdown 됩니다.
실제 특정 어플을 오래쓰다 보면 위와 같은 현상을 경험할 수 있습니다.
어플리케이션이 종료되는 것을 넘어서 아이폰 자체까지 종료되면 그 나큰 일이 아닐 수 없습니다.
이 모든 원인 ChildViewController의 delegate를 프로퍼티 속성을 retain으로 해놓았기 때문입니다.
이 문제를 해결하기 위해서는 간단합니다.
ChildViewController에서
@property (retain, nonatomic) UIViewController * delegate;
위의 부분을 아래와 같이 고치시면 됩니다.
@property (assign, nonatomic) UIViewController * delegate;
그리고 ChildViewController의 dealloc 함수에서는 아래의 부분을 제거하시면 됩니다.
[delegate release];
만약 delegate를 release 하면 ParentViewController의 retainCount가 0으로 되고, ParentViewContrller의 dealloc 함수를 호출하면서 ChildViewController의 인스턴스를 release하려하고, 이런 식으로 계속 진행되어 무한루프에 빠지게 됩니다.
그럴리 없다구요? 의심이 가시면 한번 해보세요!
많은 iPhone 개발하시는 분이 @property 속성이 retain만 있다고 생각하시는데
그렇지 않구요.
copy, assign 이 더 있습니다.
3개의 속성이 어떻게 다른지는 아래의 아티클에서 확인 하시면 될 것 같습니다.
제가 오늘 드리는 TIP은 delegate 디자인 패턴에서는 assign 속성을 사용하시라는 겁니다.
http://iphone.detailsfinder.com/en/2010/difference-assign-retain-copy-property/
"프로그래밍 / iPhone" 분류의 다른 글
| [iPhone 개발 Tip #4] 카메라 AF 상태 체크하기 (4) | 2010/06/07 |
| [iPhone 개발 Tip #3] 새로운 XCODE를 다운 받았을 경우 (4) | 2010/06/02 |
| Apple 개발자 등록 프로그램 종류와 가격 (0) | 2010/03/10 |
| iPhone 3GS vs Nexus One (0) | 2010/01/18 |
| iPhone 3GS 를 사용하다~ SKT 개통? (2) | 2009/08/11 |
| iPhone에서 ARToolkit 사용하기 (12) | 2009/08/03 |
| iPhone 3.1 beta 3에서 camera 변경된 점 (10) | 2009/07/29 |
| iPhone에도 3.1부터 AR(증강현실)을 사용가능? (2) | 2009/07/27 |

댓글을 달아 주세요
chaoskcuf님의 블로그 방문 정말 다행입니다.
마침 제가 하는 일에 난감한 문제들이 생겨 걱정이었는데 경험 있고 열정적인 님과 같은 분의 블로그를 알게 되어 너무 반갑습니다.
제가 하는 일은 엔진개발이 기본이었구요.
이번에 iPhone에 온라인 한글인식기 엔진을 올려볼가 하는게 기본 목적이고..
전 Mobile 쪽은 영 깜깜입니다.
아직 개발환경구축도 못한 상태라 이렇타하게 질문을 드리긴 힘들지만..
(인식엔진은 MAC 용으로 넘긴다 해도 그 다음이 문제...)
앞으로 애로되는 문제들에 대해 님께 문의 드려볼가 생각합니다.
많은 도움 주셨으면 고맙겠습니다.
하하 ^^;
도움되는 글이 많았으면 좋겠네요~ ^^
안녕하세요?
아이폰 키보드와 관련하여 chaoskcuf님께 한가지 문의하려고 합니다.
아이폰에서 키보드는 아무 텍스트 필드나 터치하면 자동 나타나지 않나요.
헌데 이 키보드를 자작 키보드로 대치하려고 합니다.
키보드는 iOS에서 제공하는 듯 한데 키보드가 뜨는 시점이라던가, 그리고 다른 모듈(인터페이스까지 포함)로 대치할수 있는 방법이 없나요?
혹은 관련 자료나 링크라도 알고 계시면 조언 부탁드리겠습니다.
http://itunes.apple.com/kr/app/custom-keyboard/id327466789?mt=8 이런 어플들이 등록된 것 봐서는 당연히 방법은 존재하는 것 같네요. http://stackoverflow.com/questions/1610 ··· keyboard 이 글을 참고해보세요
와우~~ 대단히 감사합니다.
아직 명확히 이러저러하게 해보지도 못하고 (아직 초보라서..) 학습을 병행하며 개발을 갓, 막 시작하며
제품의 최종구현을 위해 걱정되는 문제를 문의드렸는데 성의있는 답변 너무 감사합니다.
많은 참고가 될것 같습니다.
아울러 충분한 품을 들이지 않고 성의없이 질문을 올린 것에 사과의 마음 전하고요,
다시한번 감사를 드립니다.
앞으로 개발을 하면서 제기되는 문제들에 님의 방조 많이 받아야 할 것 같습니다.
내용이 길어지면 메일로 연락함도 어떻겠는지요?
바쁜 시간 내어 방조를 주어 고맙습니다. 꾸벅^^
별 말씀을요 저도 궁금했던 사항입니다 ^^
모르시는거 있으면 언제든지 물어보세요~
관리자만 볼 수 있는 댓글입니다.
메일 주소를 비밀글로 남겨주세요~!
제가 볼때는 큰문제가 없어 보이는 코드인듯 한데요?
retain을 해줬으니 dealloc할때 release해주면 카운트만 -1되고 실재 ParentViewController가 사라질때 자신을 생성한 쪽의 dealloc에서 release해주면 깨끗히 release되는것 아닌지요?
사실 전 메모리 문제 때문에 보게 된게 아니라
상위view의 controller의 사용 문제를 찾다가 우연치 않게 보게 됐내요
맨땅에 해딩하면서 하다 보니까 기본 개념이 많이 부족한 상태에서 진행하고 있었는대
본 문서가 많은 도움이 됐내요 덕분에 3일동안 해매던 문제를
해결하게 됐습니다. 감사 합니다. _(_ _)_