2014년 5월 9일 금요일

[iOS/OSX] NSNotification, NSNotificationCenter 기초 가이드

말이 약간 어렵지만 '앱 내부에 시그널 패스가 연결되지 않은 곳에 메시지를 어떻게 던질 것인가' 라는 의문이 나온다면 NSNotificationCenter 를 쓰는 것이 답이다. 좀 더 단순히 이야기 하자면, 앱 내에서 아무 데서나 메시지를 던지면 앱 내의 아무데서나 이 메시지를 받을 수 있게 해 주는 것이 NSNotificationCenter 의 역활이다. 그리고 이 메시지 내용을 감싸는 껍데기가 NSNotification 오브젝트이다.

말이 좀 어려운데 어떻게 해야 더 쉽게 표현이 가능 할 지 모르겠다. 어쨌거나 NSNotificationCenter를 이용하는 방법의 기초에 관한 기록을 남긴다.

NSNotificationCenter

이름에서 보듯이 Notification Center 이다. 즉 NSNotification 을 중계해 주는 역활이다. 일반적으로 오브젝트를 생성해서 사용하지는 않고 대신 편하게 싱글턴 인스턴스를 받아 사용한다.
// Objective-C Example: NSNotificationCenter Singleton Pattern
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];

// Swift Example: NSNotificationCenter Singleton Pattern
let notificationCenter = NSNotificationCenter.defaultCenter()
굳이 변수에 오브젝트 포인터를 저장 해 둘 필요는 없다. 필요 할 때 바로 싱글턴 팩토리(defaultCenter)를 불러서 쓰면 된다.

Observer 등록

Observer 라는 이름을 스타크래프트에서 많이 들어봤을 것이다. 투명하게 정찰하는 작고 귀여운...... 은 생략하고, 옵저버란 중계되는 NSNotification 중 원하는 것을 골라서 받을 수 있게 해 주는 기능을 한다.

우선 옵저버 몸뚱아리를 먼저 만들어 보자. 그냥 메서드 하나 만들고 인자로 NSNotification 오브젝트를 받도록 하면 된다.
// Objective-C Example: Notification Handler Method
- (void)didReceiveSimpleNotification:(NSNotification *)notification
{
    NSString *message = [notification.userInfo objectForKey:@"message"];
    NSLog(@"I've got the message %@", message);
}

// Swift Example: Notification Handler Method
func didReceiveSimpleNotification(notification: NSNotification) {
    let message: String? = notification.userInfo["message"] as? String
    println("I've got the message \(message)")
}
위 코드는 NSNotification 을 통해 문자열(message)을 전달받아 로그를 찍는 코드이다. 여기서 userInfo 라는 Dictionary(사전)형 오브젝트를 마음껏 이용 할 수 있다는 점을 알아두자.

아래 코드는 "simple-notification" 이라는 이름의 Notification 을 받으면 위에서 만든 didReceiveSimpleNotification: 메서드를 호출하도록 옵저버를 등록하는 예제이다.
// Objective-C Example: Add Notification Observer with Selector
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(didReceiveSimpleNotification:)
                                             name:@"simple-notification"
                                           object:nil];

// Swift Example: Add Notification Observer with Selector
let nc = NSNotificationCenter.defaultCenter()
nc.addObserver(self, 
               selector: "didReceiveSimpleNotification:", 
               name: "simple-notification", 
               object: nil)
마지막의 object 를 제외하고는 용도가 분명해 보인다. 개인적으로 object는 잘 사용하지 않기에 nil로 명시하고 있지만 필요하다면 레퍼런스 매뉴얼에서 용도를 파악해 보자.

여기까지면 옵저버 등록이 끝난다. 하지만 마지막으로 위의 두 가지 코드를 한 번에 블럭을 이용해 옵저버를 등록하는 방법도 있다.
// Objective-C Example: Add Notification Observer with Block
[[NSNotificationCenter defaultCenter] addObserverForName:@"simple-notification" 
                                                  object:nil 
                                                   queue:[NSOperationQueue mainQueue] 
                                              usingBlock:^(NSNotification *note) {
    NSString *message = [note.userInfo objectForKey:@"message"];
    NSLog(@"I've got the message %@", message);
}];

// Swift Example: Add Notification Observer with Closure
nc.addObserverForName("simple-notification", 
                      object: nil, 
                      queue: NSOperationQueue.mainQueue(), 
                      usingBlock: { (n: NSNotification!) -> () in
    let message: String? = n.userInfo["message"] as? String
    println("I've got the message \(message)")
})
위의 메서드를 분리해서 셀렉터를 등록하는 것에 비해 훨신 쓰기에는 편해 보인다. 하지만 queue 라는게 쓰이고 있는 점을 주의해야 한다. 위의 예제에서는 mainQueue를 불러다 썼기 때문에 UI 업데이트에 문제가 없겠지만 만약 다른 NSOperationQueue 를 만들어서 이걸 쓰고 싶다면 필요할 때 메인스레드를 사용하도록 잘 처리해야 한다.

Post Notification

이제 옵저버를 만들고 옵저버가 Notification 메세지를 받을 수 있도록 해 놨으니 이제는 Notification 을 쏴 볼 차례다. 아래 코드는 "simple-notification" 라는 이름의 Notification을 쏘는 예제이다.
// Objective-C Example: Post Notification
NSDictionary *userInfo = @{ @"message": @"Message using notification..." };
[[NSNotificationCenter defaultCenter] postNotificationName:@"simple-notification" 
                                                    object:nil 
                                                  userInfo:userInfo];

// Swift Example: Post Notification
let nc = NSNotificationCenter.defaultCenter()
let userInfo = [ "message": "Message using Notification" ]
nc.postNotificationName("simple-notification", object: nil, userInfo: userInfo)
"message" 라는 키에 원하는 데이터를 넣고 이를 이용해 NSDictionary 사전형 객체를 만들고 이를 userInfo에 넣고 Notification 을 Post 한다. 간단히 말해 멋대로 만든 userInfo 로 메세지를 쏜다는 말이다. userInfo란 원래 이런 용도로 쓰는 거니깐. :-)

이번에도 object 는 nil 로 안쓴다고 표기했는데 역시나 관심이 있다면 레퍼런스를 찾아서 활용도를 찾아보자.

Remove Observer

NSNotificationCenter 는 사용 방법이 간단해서 쉽게 쓸 수 있었는데 주의해야 할 점이 있다. 옵저버는 특정 클래스 오브젝트의 것이지만 NSNotificationCenter 는 싱글턴 인스턴스라서 여러 오브젝트에서 공유한다. 그래서 옵저버를 등록한 오브젝트가 메모리에서 해제되면 NSNotificationCenter 에서도 옵저버를 없앴다고 알려줘야 된다.
// Objective-C Example: Remove Notification Observer from Deallocator
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

// Swift Example: Remove Notification Observer from deinit
deinit {
    NSNotificationCenter.defaultCenter().removeObserver(self)
}
위 코드는 단순히 NSObject 형식의 모든 클래스에서 메모리가 해제될 때 호출되는 dealloc 에 옵저버를 제거하는 코드를 넣은 예제이다. 즉 removeObserver: 를 이용해 자기 자신의 오브젝트에 속한 모든 옵저버를 NSNotificationCenter 에서 제거 할 수 있다.

만약 제대로 제거해 주지 않으면 옵저버가 소속된 오브젝트가 해제 되었을 때 Notification 이 전달되면 100% 앱이 죽는다.

물론 이 외에도 필요 할 때 특정 옵저버만 제거 할 수 있는 removeObserver:name:object: 같은 메서드도 있으니 필요하면 찾아보자.

마무리

이 Notification 의 활용도는 무궁무진하다. 예를 들어 서버와 통신하는 모듈을 별도로 만들고 UI와 완전히 구분해 놓은 코드를 작성 할 때 통신 모듈에서 UI로 데이터를 어떻게 던져줄까 고민 할 때 한 가지 방법이 될 수 있다.

그 외에 기존의 delegate 모델은 하나의 오브젝트 에서만 사용이 가능하니 여러 오브젝트에서 데이터를 전달 받아야 한다면 역시나 Notification 이 답이다.

[관련글] Notification Snippets (Swift)

댓글 없음 :