UIAlertView, delegate 그리고 ARC

며칠 전 직면했던, 지금 돌이켜 보면 사소한(?) 상황에 대해 정리한다. ARC 를 너무 생각없이 편하게만 – C#과 Java 처럼 – 사용하여 발생한 문제다.

핵심 상황은 다음과 같다.

UIViewController인 TestUIViewController가 있다. TestUIViewController는 알림 메시지(UIAlertView) 통보를 AlertViewManager에게 위임한다. AlertViewManager는 상황을 판단하여 필요한 경우 알림 메시지를 띄운다. 예시 코드는 다음과 같다.

// View Controller
@implementation TestUIViewController

-(void)alertTrigger{

    [[[AlertViewManager alloc] init] alert];
}
@end

// Alert 을 담당하는 별도 클래스
@interface AlertViewManager : NSObject

// View Controller에서 알림 메시지 호출을 위해 사용
-(void)alert;

@end

@implementation AlertViewManager

-(void)alert{

    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Alert Test" message:@"ARC가 delegate를 지울 수도 있어요" delegate:self cancelButtonTitle:@"어쩌라고" otherButtonTitles:@"땡큐", nil];

    [alertView show];

}

// UIAlertViewDelegate handler
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{

    switch (buttonIndex) {
       case 0:

          // do something
          break;

       case 1:

          // do something
          break;

       default:
          break;
     }
}

@end

코드를 실행하여 알림 메시지 창을 띄운 후, 알림 메시지창 버튼을 클릭하면 앱은 종료(Crash)된다. 문제 원인은 TestViewController에서 사용하는 AlertViewManager 객체 life cycle과 관련있다.

Alert View

TestViewController의 alertTrigger 메소드 안에서 생성된 AlertViewManager 객체는 TestViewController의 속성이나 인스턴스 변수로 할당이 되지 않고 바로 사용된다. 이로 인해 alertTrigger 메소드 종료시점에 AlertViewManager 객체는 참조 관계를 잃어 ARC에 의해 해제(release)된다.

문제는 AlertViewManager가 UIAlertViewDelegate로 할당되었다는 사실이다. delegate는 retain cycle을 피하기 위해 __weak 참조를 기본으로 하고 있다. ([iOS]Practical Memory Management 참조)

delegate 할당이 retain count를 증가시키지 않으므로 AlertViewManager 객체가 release되면 delegate=self 로 할당된 delegate 도 해제된다.  결국 알림 메시창의 버튼 클릭 이벤트를 처리할 delegate 객체가 없어진 상태에서 버튼이 클릭되어 앱이 종료된 것이다.

Instruments의 Zombie를 통해 정확히 확인할 수 있다.

UIAlertView Zombie

위 그림의 RefCt (Reference Count)의 변화를 보면 UIViewController의 메소드 (캡쳐 화면 메소드 이름과 예시 상황에서 설명한 메소드 이름이 상이하지만 이해에 문제 없을 것 같다)에서 AlertViewManager는 할당되고 해제된다. AlertView의 buttonClicked: 에서 Zombie 객체가 확인된다.

이 상황의 해결법 중 하나는  AlertViewManager를 TestUIViewController의 변수로 추가시켜 ViewController가 소멸될 때까지 AlertView release가 되지 않도록 방지하는 것이다. 수정되는 코드는 아래와 같다.

// View Controller
@implementation TestUIViewController{

    // private 필드로 지정. (인스턴스 생성 코드는 생략)
    AlertViewManager *alertManager;
}

-(void)alertTrigger{

    // 인스턴스 변수를 통해 alert 호출
    [alertManager alert];
}
@end

생각해보면 별 것도 아니고 실제로 자주 발생하는 문제도 아니다. 그러나 ARC의 편의를 누리려면  ARC에 대해 좀 더 명확히 알 필요가 있을 것 같다.

Advertisements
Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: