말이 약간 어렵지만 '앱 내부에 시그널 패스가 연결되지 않은 곳에 메시지를 어떻게 던질 것인가' 라는 의문이 나온다면 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이 답이다.