什么?你以為你通知全懂了?抱歉,你懂的僅僅是基礎(chǔ)。
一、為什么要使用NSNotification
The standard way to pass information between objects is message passing—one object invokes the method of another object. However, message passing requires that the object sending the message know who the receiver is and what messages it responds to. At times, this tight coupling of two objects is undesirable—most notably because it would join together two otherwise independent subsystems. For these cases, a broadcast model is introduced: An object posts a notification, which is dispatched to the appropriate observers through an NSNotificationCenter object, or simply notification center.
大概意思是,若想objectA需要調(diào)用objectB的一個(gè)方法,一般來(lái)說(shuō),需要objectA中能訪問(wèn)到objectB,但是這是一種不受歡迎的會(huì)造成耦合的方式。所以,引入了一個(gè)新機(jī)制,叫NSNotificationCenter,由NSNotificationCenter管理objectB等注冊(cè)的object,而objectA不需要知道objectB,只需要通過(guò)標(biāo)記(例如NotificationName)在NSNotificationCenter中找到對(duì)應(yīng)的object,從而調(diào)用該object的方法。無(wú)疑,這是一種松耦合,并且允許多對(duì)多的一種方式。
通知和delegate的基本區(qū)別:
- 通知是允許多對(duì)多的,而delegate只能是1對(duì)1的。
- 通知是松耦合的,通知方不需要知道被通知方的任何情況,而delegate不行。
- 通知的效率比起delegate略差。
二、通知的基本使用
1、通知中的基本概念
NSNotification
// NSNotification.h相關(guān)代碼
@interface NSNotification : NSObject <NSCopying, NSCoding>
@property (readonly, copy) NSNotificationName name;
@property (nullable, readonly, retain) id object;
@property (nullable, readonly, copy) NSDictionary *userInfo;
- (instancetype)initWithName:(NSNotificationName)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo NS_AVAILABLE(10_6, 4_0) NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
@end
@interface NSNotification (NSNotificationCreation)
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
- (instancetype)init /*NS_UNAVAILABLE*/; /* do not invoke; not a valid initializer for this class */
@end
// 理解
NSNotification作為一種消息傳遞的媒介,包含三個(gè)public成員變量,通過(guò)NSNotificationName類型的name來(lái)查找對(duì)應(yīng)observer,并且可以在object和userInfo中傳入?yún)?shù)。可以使用上述的幾種初始化方式進(jìn)行初始化。
NSNotificationCenter
// 默認(rèn)的通知中心,全局只有一個(gè)
@property (class, readonly, strong) NSNotificationCenter *defaultCenter;
// 在通知中心添加觀察者
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
// 向通知中心發(fā)送通知
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
// 在通知中心移除觀察者
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;
// iOS4以后,以block的形式代替selector方式為通知中心添加觀察者
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block NS_AVAILABLE(10_6, 4_0);
** NSDistributedNotificationCenter**
// 在iOS的框架中找不到這個(gè)類相關(guān)知識(shí)。該通知中心是Mac OS中,進(jìn)程間通知使用。不多進(jìn)行介紹。
2、通知的基本使用
代碼
// 代碼
#import "AppDelegate.h"
@interface A : NSObject
- (void)test;
@end
@implementation A
static int count = 0;
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)test {
// 觀察方式A:selector方式
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(xxx) name:@"111" object:nil];
// 觀察方式B:block方式(queue參數(shù)決定你想把該block在哪一個(gè)NSOperationQueue里面執(zhí)行)
[[NSNotificationCenter defaultCenter] addObserverForName:@"111" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"block %d", ++count);
}];
}
- (void)xxx {
NSLog(@"selector %d", ++count);
}
@end
@interface AppDelegate ()
@property (nonatomic, strong) A *a;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
A *a = [A new];
[a test];
self.a = a;
// 發(fā)送方式A:手動(dòng)定義NSNotification
NSNotification *noti = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationCenter defaultCenter] postNotification:noti];
// 發(fā)送方式B:自動(dòng)定義NSNotification
[[NSNotificationCenter defaultCenter] postNotificationName:@"111" object:nil userInfo:nil];
return YES;
}
@end
// 輸出
2017-02-26 19:00:27.461 notification[14092:12661907] selector 1
2017-02-26 19:00:27.462 notification[14092:12661907] block 2
2017-02-26 19:00:27.462 notification[14092:12661907] selector 3
2017-02-26 19:00:27.462 notification[14092:12661907] block 4
理解
兩個(gè)觀察者,兩個(gè)發(fā)送者,可見(jiàn)有四個(gè)log信息,可以說(shuō)明,通知支持這種多對(duì)多的消息傳遞。
3、疑難點(diǎn)
** 同步or異步**
同步和異步都是相對(duì)于發(fā)送通知所在的線程的。可以通過(guò)下述代碼簡(jiǎn)單測(cè)出:
// 替換上述的didFinishLaunchingWithOptions函數(shù)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
A *a = [A new];
[a test];
self.a = a;
// 發(fā)送方式A:手動(dòng)定義NSNotification
NSNotification *noti = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationCenter defaultCenter] postNotification:noti];
// 發(fā)送方式B:自動(dòng)定義NSNotification
[[NSNotificationCenter defaultCenter] postNotificationName:@"111" object:nil userInfo:nil];
NSLog(@"測(cè)試同步還是異步");
return YES;
}
輸出總是為:
2017-02-26 19:13:04.739 notification[15240:12674807] selector 1
2017-02-26 19:13:04.743 notification[15240:12674807] block 2
2017-02-26 19:13:04.743 notification[15240:12674807] selector 3
2017-02-26 19:13:04.744 notification[15240:12674807] block 4
2017-02-26 19:13:04.744 notification[15240:12674807] 測(cè)試同步還是異步
可以看出,postNotification:總是會(huì)卡住當(dāng)前線程,待observer執(zhí)行(如不特殊處理selector也會(huì)在postNotification:所在線程執(zhí)行)結(jié)束之后才會(huì)繼續(xù)往下執(zhí)行。所以是同步的。
忘記remove的問(wèn)題
這個(gè)就不進(jìn)行測(cè)試了,因?yàn)槲乙矝](méi)有iOS8以及之前的設(shè)備。直接寫(xiě)結(jié)論了:
- 若在iOS8或之前版本系統(tǒng)中,對(duì)一個(gè)對(duì)象addObserver:selector:name:object:(假設(shè)name為@“111”),但是并沒(méi)有在dealloc的之前或之中,對(duì)其進(jìn)行remove操作。那么,在發(fā)送通知(name為@“111”)的時(shí)候,會(huì)因?yàn)閎ad_access(野指針)而crash。
- 若在iOS9及以后,同上操作,不會(huì)crash。
iOS8及以前,NSNotificationCenter持有的是觀察者的unsafe_unretained指針(可能是為了兼容老版本),這樣,在觀察者回收的時(shí)候未removeOberser,而后再進(jìn)行post操作,則會(huì)向一段被回收的區(qū)域發(fā)送消息,所以出現(xiàn)野指針crash。而iOS9以后,unsafe_unretained改成了weak指針,即使dealloc的時(shí)候未removeOberser,再進(jìn)行post操作,則會(huì)向nil發(fā)送消息,所以沒(méi)有任何問(wèn)題。
三、Notification Queues和異步通知
1、異步通知的原理
創(chuàng)建一個(gè)NSNotificationQueue隊(duì)列(first in-first out),將定義的NSNotification放入其中,并為其指定三種狀態(tài)之一:
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, // 當(dāng)runloop處于空閑狀態(tài)時(shí)post
NSPostASAP = 2, // 當(dāng)當(dāng)前runloop完成之后立即post
NSPostNow = 3 // 立即post,同步(為什么需要這種type,且看三.3)
};
這樣,將NSNotification放入queue,然后根據(jù)其type,NSNotificationQueue在合適的時(shí)機(jī)將其post到NSNotificationCenter。這樣就完成了異步的需求。
2、異步通知的使用
// 替換上述的didFinishLaunchingWithOptions函數(shù)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
A *a = [A new];
[a test];
self.a = a;
NSNotification *noti = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP];
//[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostWhenIdle];
//[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostNow];
NSLog(@"測(cè)試同步還是異步");
return YES;
}
// 輸出
2017-02-26 19:56:32.805 notification[19406:12719309] 測(cè)試同步還是異步
2017-02-26 19:56:32.816 notification[19406:12719309] selector 1
2017-02-26 19:56:32.816 notification[19406:12719309] block 2
上述的輸出可以看出,這樣確實(shí)完成了異步通知的需求。當(dāng)然,如果將type改成NSPostNow,則還是同步執(zhí)行,效果和不用NSNotificationQueue相同。
3、Notification Queues的合成作用
NSNotificationQueue除了有異步通知的能力之外,也能對(duì)當(dāng)前隊(duì)列的通知根據(jù)NSNotificationCoalescing類型進(jìn)行合成(即將幾個(gè)合成一個(gè))。
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
NSNotificationNoCoalescing = 0, // 不合成
NSNotificationCoalescingOnName = 1, // 根據(jù)NSNotification的name字段進(jìn)行合成
NSNotificationCoalescingOnSender = 2 // 根據(jù)NSNotification的object字段進(jìn)行合成
};
可以通過(guò)以下兩種方式進(jìn)行對(duì)比:
// 方式1:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
A *a = [A new];
[a test];
self.a = a;
NSNotification *noti = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP coalesceMask:NSNotificationNoCoalescing forModes:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:nil];
NSLog(@"測(cè)試同步還是異步");
return YES;
}
// 方式2:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
A *a = [A new];
[a test];
self.a = a;
NSNotification *noti = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostWhenIdle coalesceMask:NSNotificationCoalescingOnName forModes:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostNow coalesceMask:NSNotificationCoalescingOnName forModes:nil];
NSLog(@"測(cè)試同步還是異步");
return YES;
}
// 輸出
// 方式一
2017-02-26 20:09:31.834 notification[20612:12733161] selector 1
2017-02-26 20:09:31.835 notification[20612:12733161] block 2
2017-02-26 20:09:31.835 notification[20612:12733161] 測(cè)試同步還是異步
2017-02-26 20:09:31.851 notification[20612:12733161] selector 3
2017-02-26 20:09:31.851 notification[20612:12733161] block 4
2017-02-26 20:09:31.854 notification[20612:12733161] selector 5
2017-02-26 20:09:31.855 notification[20612:12733161] block 6
// 方式二
2017-02-26 20:11:31.186 notification[20834:12736113] selector 1
2017-02-26 20:11:31.186 notification[20834:12736113] block 2
2017-02-26 20:11:31.186 notification[20834:12736113] 測(cè)試同步還是異步
大概完成了合成的需求,但是此處還有一個(gè)疑問(wèn),比如三個(gè)通知合成一個(gè),那么實(shí)際發(fā)送的NSNotification到底是怎樣的呢?留給讀者們自己挖掘吧。
四、指定Thread處理通知
需求
假設(shè)將didFinishLaunchingWithOptions改為:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
A *a = [A new];
[a test];
self.a = a;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
NSLog(@"current thread %@", [NSThread currentThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:@"111" object:nil];
});
return YES;
}
可知,a中的observer的selector將會(huì)在DISPATCH_QUEUE_PRIORITY_BACKGROUND中執(zhí)行,若該selector執(zhí)行的是刷新UI的操作,那么這種方式顯然是錯(cuò)誤的。這里,我們需要保證selector永遠(yuǎn)在mainThread執(zhí)行。所以,有以下兩種方式,指定observer的回調(diào)方法的執(zhí)行線程。
解決方式1:NSMachPort
// 代碼
#import <CoreFoundation/CFRunLoop.h>
@interface A : NSObject <NSMachPortDelegate>
@property NSMutableArray *notifications;
@property NSThread *notificationThread;
@property NSLock *notificationLock;
@property NSMachPort *notificationPort;
- (void)setUpThreadingSupport;
- (void)handleMachMessage:(void *)msg;
- (void)processNotification:(NSNotification *)notification;
- (void)test;
@end
@implementation A
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)test {
[self setUpThreadingSupport];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"111" object:nil];
}
- (void)setUpThreadingSupport {
if (self.notifications) {
return;
}
self.notifications = [[NSMutableArray alloc] init];
self.notificationLock = [[NSLock alloc] init];
self.notificationThread = [NSThread mainThread];
self.notificationPort = [[NSMachPort alloc] init];
[self.notificationPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:self.notificationPort
forMode:(NSString *)kCFRunLoopCommonModes];
}
- (void)handleMachMessage:(void *)msg {
[self.notificationLock lock];
while ([self.notifications count]) {
NSNotification *notification = [self.notifications objectAtIndex:0];
[self.notifications removeObjectAtIndex:0];
[self.notificationLock unlock];
[self processNotification:notification];
[self.notificationLock lock];
};
[self.notificationLock unlock];
}
- (void)processNotification:(NSNotification *)notification {
if ([NSThread currentThread] != self.notificationThread) {
[self.notificationLock lock];
[self.notifications addObject:notification];
[self.notificationLock unlock];
[self.notificationPort sendBeforeDate:[NSDate date]
components:nil
from:nil
reserved:0];
} else {
NSLog(@"current thread %@ 刷新UI", [NSThread currentThread]);
// 刷新UI ...
}
}
@end
// 輸出
2017-02-27 11:49:04.296 notification[29036:12827315] current thread <NSThread: 0x7be3e000>{number = 3, name = (null)}
2017-02-27 11:49:04.307 notification[29036:12827268] current thread <NSThread: 0x7bf3b970>{number = 1, name = main} 刷新UI
由輸出可知,雖然post不是在主線程,但是刷新UI確實(shí)是在主線程,達(dá)成需求。這是官方文檔提供的一種方式,就不具體解釋了,也很容易看懂。不懂去看文檔吧。
解決方式2:block方式addObserver
文檔提供的方式一難用且冗長(zhǎng),這里我們可以用下述方式替代。
// 代碼
@interface A : NSObject <NSMachPortDelegate>
- (void)test;
@end
@implementation A
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)test {
[[NSNotificationCenter defaultCenter] addObserverForName:@"111" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"current thread %@ 刷新UI", [NSThread currentThread]);
// 刷新UI ...
}];
}
@end
// 輸出
current thread <NSThread: 0x7bf29110>{number = 3, name = (null)}
2017-02-27 11:53:46.531 notification[29510:12833116] current thread <NSThread: 0x7be1d6f0>{number = 1, name = main} 刷新UI
完美!方便!
五、通知實(shí)現(xiàn)解析
這里我參見(jiàn)的是偽源碼。
源碼很多,不一一解釋,我關(guān)注的只有兩個(gè)點(diǎn):
- observer的對(duì)象存儲(chǔ)在何處?
- post的時(shí)候根據(jù)何種方式查找接受通知的對(duì)象?
observer的對(duì)象存儲(chǔ)在何處
// NSNotificationCenter的init方法
- (id) init
{
if ((self = [super init]) != nil)
{
_table = newNCTable();
}
return self;
}
這也就很容易看出,每個(gè)NSNotificationCenter都有一個(gè)默認(rèn)的_table。其對(duì)observer進(jìn)行引用(iOS9以前unsafe_unretained,iOS9以后weak)。
post的時(shí)候根據(jù)何種方式查找接受通知的對(duì)象
// postNotification的部分代碼
/*
* Find the observers that specified OBJECT, but didn't specify NAME.
*/
if (object) {
n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
if (n != 0) {
o = purgeCollectedFromMapNode(NAMELESS, n);
while (o != ENDOBS) {
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
}
}
}
/*
* Find the observers of NAME, except those observers with a non-nil OBJECT
* that doesn't match the notification's OBJECT).
*/
if (name) {
n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
if (n) {
m = (GSIMapTable)n->value.ptr;
} else {
m = 0;
}
if (m != 0) {
/*
* First, observers with a matching object.
*/
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n != 0) {
o = purgeCollectedFromMapNode(m, n);
while (o != ENDOBS) {
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
}
}
if (object != nil) {
/*
* Now observers with a nil object.
*/
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);
if (n != 0) {
o = purgeCollectedFromMapNode(m, n);
while (o != ENDOBS) {
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
}
}
}
}
}
不用完全看懂這塊代碼,只需要看出,在table中查找observer object的時(shí)候,首先根據(jù)的是object,接下來(lái)根據(jù)的是name,可見(jiàn)name的優(yōu)先級(jí)比較高。
六、拾遺
1、那些系統(tǒng)的通知Name
// 當(dāng)程序被推送到后臺(tái)時(shí)
UIKIT_EXTERN NSNotificationName const UIApplicationDidEnterBackgroundNotification NS_AVAILABLE_IOS(4_0);
// 當(dāng)程序從后臺(tái)將要重新回到前臺(tái)時(shí)
UIKIT_EXTERN NSNotificationName const UIApplicationWillEnterForegroundNotification NS_AVAILABLE_IOS(4_0);
// 當(dāng)程序完成載入后通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidFinishLaunchingNotification;
// 應(yīng)用程序轉(zhuǎn)為激活狀態(tài)時(shí)
UIKIT_EXTERN NSNotificationName const UIApplicationDidBecomeActiveNotification;
// 用戶按下主屏幕按鈕調(diào)用通知,并未進(jìn)入后臺(tái)狀態(tài)
UIKIT_EXTERN NSNotificationName const UIApplicationWillResignActiveNotification;
// 內(nèi)存較低時(shí)通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidReceiveMemoryWarningNotification;
// 當(dāng)程序?qū)⒁顺鰰r(shí)通知
UIKIT_EXTERN NSNotificationName const UIApplicationWillTerminateNotification;
// 當(dāng)系統(tǒng)時(shí)間發(fā)生改變時(shí)通知
UIKIT_EXTERN NSNotificationName const UIApplicationSignificantTimeChangeNotification;
// 當(dāng)StatusBar框方向?qū)⒁兓瘯r(shí)通知
UIKIT_EXTERN NSNotificationName const UIApplicationWillChangeStatusBarOrientationNotification __TVOS_PROHIBITED; // userInfo contains NSNumber with new orientation
// 當(dāng)StatusBar框方向改變時(shí)通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidChangeStatusBarOrientationNotification __TVOS_PROHIBITED; // userInfo contains NSNumber with old orientation
// 當(dāng)StatusBar框Frame將要改變時(shí)通知
UIKIT_EXTERN NSNotificationName const UIApplicationWillChangeStatusBarFrameNotification __TVOS_PROHIBITED; // userInfo contains NSValue with new frame
// 當(dāng)StatusBar框Frame改變時(shí)通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidChangeStatusBarFrameNotification __TVOS_PROHIBITED; // userInfo contains NSValue with old frame
// 后臺(tái)下載狀態(tài)發(fā)生改變時(shí)通知(iOS7.0以后可用)
UIKIT_EXTERN NSNotificationName const UIApplicationBackgroundRefreshStatusDidChangeNotification NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED;
// 受保護(hù)的文件當(dāng)前變?yōu)椴豢捎脮r(shí)通知
UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataWillBecomeUnavailable NS_AVAILABLE_IOS(4_0);
// 受保護(hù)的文件當(dāng)前變?yōu)榭捎脮r(shí)通知
UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataDidBecomeAvailable NS_AVAILABLE_IOS(4_0);
// 截屏通知(iOS7.0以后可用)
UIKIT_EXTERN NSNotificationName const UIApplicationUserDidTakeScreenshotNotification NS_AVAILABLE_IOS(7_0);
2、通知的效率
該用就用,不用特別在意。
七、文獻(xiàn)
1、https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html#//apple_ref/doc/uid/10000043-SW1
2、https://github.com/gnustep/base/tree/master/Source