一、NSNotification 的基本使用 1、NSNotification NSNotification
的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @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 API_AVAILABLE(macos(10.6 ), ios(4.0 ), watchos(2.0 ), tvos(9.0 )) NS_DESIGNATED_INITIALIZER ; - (nullable instancetype )initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER ; @end
可以看到 NSNotification
中有一个初始化方法,除了使用该方法进行初始化,还可以使用 NSNotification(NSNotificationCreation)
分类中定义的初始化方法:
1 2 3 4 5 6 7 8 @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 ; @end
在 NSNotification(NSNotificationCreation
) 分类中对 init
方法注释提示不要用来初始化。如果使用 init
方法初始化 NSNotification
会发生如下 Crash: 不过,开发时一般不会手动创建 NSNotification
,而是使用 NSNotificationCenter
创建并发送通知。
2、NSNotificationCenter NSNotificationCenter
提供了一个 defaultCenter
属性,用于获取 NSNotificationCenter
单例,并且提供了添加观察者、发送通知、移除通知的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @property (class , readonly , strong ) NSNotificationCenter *defaultCenter;- (void )addObserver:(id )observer selector:(SEL)aSelector name:(nullable NSNotificationName )aName object:(nullable id )anObject; - (id <NSObject >)addObserverForName:(nullable NSNotificationName )name object:(nullable id )obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block; - (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;
发送通知的时候,可以指定 notificationName
和 object
:
若 notificationName
为 nil
,通知中心会通知所有与该通知中 object
相匹配的监听对象;
若 anObject
为 nil
,通知中心会通知所有与该通知中 notificationName
相匹配的监听对象。
如果 notificationName
与 anObject
均为 nil
,监听者将会收到所有发出的通知。
可以看到,添加通知观察者除了我们常用的 addObserver:selector:name:object:
外,还有个 addObserverForName:object:queue:usingBlock:
,这两种方式有什么区别呢?
NSNotification
的发送和通知接收回调是在同一线程的,这点可以通过一个例子验证下:
1 2 3 4 5 6 7 8 9 10 - (IBAction )btnClick:(id )sender { dispatch_async (dispatch_get_global_queue(0 , 0 ), ^{ NSLog (@"发送通知:%@" , [NSThread currentThread]); [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationName object:nil ]; }); } - (void )notificationAction:(NSNotification *)notification { NSLog (@"收到通知:%@" , [NSThread currentThread]); }
打印结果:
1 2 收到通知:<NSThread: 0x6000036a2900>{number = 7, name = (null)} 发送通知:<NSThread: 0x6000036a2900>{number = 7, name = (null)}
但是,我们有时候是在子线程发出通知,想要在收到通知的时候在主线程更新 UI,除了在收到通知后主动切换到主线程外,还可以使用 addObserverForName:object:queue:usingBlock:
方法实现,该方法可以指定一个收到通知的 block
在哪个队列执行,我们可以指定主队列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 - (void )viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserverForName:kNotificationName object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { NSLog (@"收到通知:%@" , [NSThread currentThread]); }]; } - (IBAction )btnClick:(id )sender { dispatch_async (dispatch_get_global_queue(0 , 0 ), ^{ NSLog (@"发送通知:%@" , [NSThread currentThread]); [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationName object:nil ]; }); }
打印结果:
1 2 发送通知:<NSThread: 0x600001420f00>{number = 5, name = (null)} 收到通知:<NSThread: 0x600001464880>{number = 1, name = main}
可以看到,我们在子线程发出通知,但是收到通知的 block
是在主线程的。
这两个方法添加监听者还有一个很重要的区别,就是在 iOS 9 之后,addObserver:selector:name:object:
添加的监听者,在监听者对象释放之前不再需要手动移除监听(iOS 9 之前是需要的)。这是因为在 iOS 9 以后,通知中心持有的观察者由 unsafe_unretained
引用变为 weak
引用,unsafe_unretained
就是 iOS 5 版本以下的 assign
,assign
与 unsafe_unretained
修饰对象会产生野指针的问题,修饰的对象释放后,指针不会自动置成 nil
,此时再向对象发消息程序会崩溃。而 weak 会在对象释放后置为 nil
,所以 iOS 9 之后,不移除监听者也不会引起访问野指针的 Crash 了。
Apple 文档涉及到这块的描述:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don’t need to unregister an observer in its dealloc method. Otherwise, you should call removeObserver:name:object: before observer or any object passed to this method is deallocated.
而 addObserverForName:object:queue:usingBlock:
方法添加的监听在 iOS 9 之后仍然需要手动移除监听,因为 NSNotificationCenter
对该方法注册的监听者是强引用,如不移除虽然不会引起 Crash,但是会导致内存泄露。
Apple 文档涉及到这块的描述:
To unregister observations, you pass the object returned by this method to removeObserver:. You must invoke removeObserver: or removeObserver:name:object:before any object specified by addObserverForName:object:queue:usingBlock:is deallocated.
文档中也已经指出,addObserverForName:object:queue:usingBlock:
会有一个返回值,将该返回值传给 removeObserver:
方法即可移除监听。
3、NSNotificationQueue NSNotificationQueue
用于通知消息的管理,如消息发送时机、消息合并策略,并且为 FIFO(先入先出)方式管理消息,但实际消息的发送仍然是在指定时机自动使用 NSNotificationCenter
发送的。每个线程都有一个默认的通知队列,即 NSNotificationQueue
,它与 APP 默认的 NSNofitifcationCenter
关联。
NSNotificationQueue
中定义的属性与方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 @property (class , readonly , strong ) NSNotificationQueue *defaultQueue;- (instancetype )initWithNotificationCenter:(NSNotificationCenter *)notificationCenter NS_DESIGNATED_INITIALIZER ; - (void )enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle )postingStyle; - (void )enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle )postingStyle coalesceMask:(NSNotificationCoalescing )coalesceMask forModes:(nullable NSArray <NSRunLoopMode > *)modes; - (void )dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger )coalesceMask;
向队列中添加通知时的参数含义如下:postingStyle:
用于配置通知什么时候发送
NSPostASAP
:Posting As Soon As Possible,在当前通知调用或者计时器结束发送通知。发送时机早于 NSPostWhenIdle
,kCFRunLoopBeforeTimers
与 kCFRunLoopBeforeSources
之间发送通知。
NSPostWhenIdle
:当 runloop 处于空闲时发出通知。在 kCFRunLoopBeforeWaiting
与 kCFRunLoopAfterWaiting
之间发生通知。
NSPostNow
:在合并通知完成之后立即发出通知。
需要注意的是,如果 postingStyle
参数不是 NSPostNow
,并且是在子线程,需要保证子线程 runloop 已经开启;如果是 NSPostNow
,则和直接使用 NSNotificationCenter
发送通知效果一样无需开启 runloop。
coalesceMask
(注意这是一个 NS_OPTIONS
参数):用于配置如何合并通知,即移除重复的通知
NSNotificationNoCoalescing
:不合并通知(无论 name
和 object 是否相同都不移除通知)
NSNotificationCoalescingOnName
:按照通知名字合并通知
NSNotificationCoalescingOnSender
:按照传入的 object
合并通知
modes
:runloop 的 mode,指定 mode 后,只有当前线程的 runloop 在指定的 mode 下才能将通知自动发送到通知中心。
例如,将通知添加到队列,并指定 runloop 空闲时发送通知:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 static NSString * const kNotificationName = @"kNotificationName" ;@interface ViewController ()@property (nonatomic , strong ) NSThread *thread;@end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector (notificationAction:) name:kNotificationName object:nil ]; } - (void )notificationAction:(NSNotification *)notification { NSLog (@"收到通知:%@" , [NSThread currentThread]); } - (IBAction )btnClick:(id )sender { [self performSelector:@selector (postNotification) onThread:self .thread withObject:nil waitUntilDone:YES ]; } - (void )postNotification { NSNotification *notification = [NSNotification notificationWithName:kNotificationName object:nil ]; [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode ]]; } - (NSThread *)thread { if (!_thread) { _thread = [[NSThread alloc] initWithBlock:^{ CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler (CFAllocatorGetDefault (), kCFRunLoopAllActivities, YES , 0 , ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: NSLog (@"kCFRunLoopEntry" ); break ; case kCFRunLoopBeforeTimers: NSLog (@"kCFRunLoopBeforeTimers" ); break ; case kCFRunLoopBeforeSources: NSLog (@"kCFRunLoopBeforeSources" ); break ; case kCFRunLoopBeforeWaiting: NSLog (@"kCFRunLoopBeforeWaiting" ); break ; case kCFRunLoopAfterWaiting: NSLog (@"kCFRunLoopAfterWaiting" ); break ; case kCFRunLoopExit: NSLog (@"kCFRunLoopExit" ); break ; default : break ; } }); CFRunLoopAddObserver (CFRunLoopGetCurrent (), observer, kCFRunLoopCommonModes); CFRelease (observer); [[NSRunLoop currentRunLoop] run]; }]; [_thread start]; } return _thread; } @end
按钮点击打印结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 kCFRunLoopEntry kCFRunLoopBeforeTimers kCFRunLoopBeforeSources kCFRunLoopExit kCFRunLoopEntry kCFRunLoopBeforeTimers kCFRunLoopBeforeSources kCFRunLoopBeforeWaiting 收到通知:<NSThread: 0x600000f13c80>{number = 7, name = (null)} kCFRunLoopAfterWaiting kCFRunLoopBeforeTimers kCFRunLoopBeforeSources kCFRunLoopBeforeWaiting
可以看到,如果我们指定了在 runloop 空闲时发送通知,则会在 runloop 即将进入休眠时发送通知。
二、NSNotification 实现原理 由于苹果对 NSNotification
是不开源的,GNUstep 将 Cocoa 的 OC 库重新开源实现了一遍,虽然 GNUstep(https://github.com/gnustep/libs-base) 不是苹果官方源码,但还是具有一定的参考价值。
根据 GNUstep 相关源码可知,NSNotificationCenter
中持有了一个 _table
:
1 2 3 4 5 6 7 8 9 @interface NSNotificationCenter : NSObject { @private void *_table; } @end
在 NSNotificationCenter.m 中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @implementation NSNotificationCenter - (id ) init { if ((self = [super init]) != nil ) { _table = newNCTable(); } return self ; } static NCTable *newNCTable(void ) { NCTable *t; return t; } @end
可以知道,NSNotificationCenter
持有了一个 NCTable
类型的 _table
,NCTable
是一个结构体,简化后的结构如下:
1 2 3 4 5 6 typedef struct NCTbl { Observation *wildcard; GSIMapTable nameless; GSIMapTable named; } NCTable;
其中 Observation
结构如下:
1 2 3 4 5 6 7 typedef struct Obs { id observer; SEL selector; struct Obs *next; struct NCTbl *link; } Observation;
关于其中 GSIMapTable
等详细结构,接下来从源码实现上来看。
1、addObserver:selector:name:object: 方法的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 - (void ) addObserver: (id )observer selector: (SEL)selector name: (NSString *)name object: (id )object { Observation *list; Observation *o; GSIMapTable m; GSIMapNode n; ...... o = obsNew(TABLE, selector, observer); if (name) { n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id )name); if (n == 0 ) { m = mapNew(TABLE); GSIMapAddPair(NAMED, (GSIMapKey)(id )name, (GSIMapVal)(void *)m); ... } else { m = (GSIMapTable)n->value.ptr; } n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object); if (n == 0 ) { o->next = ENDOBS; GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o); } else { list = (Observation*)n->value.ptr; o->next = list->next; list->next = o; } } else if (object) { n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object); if (n == 0 ) { o->next = ENDOBS; GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o); } else { ... } } else { o->next = WILDCARD; WILDCARD = o; } }
该流程总结: 根据以上各字段数据结构可知,无论是否存在 name
和 object
,最终监听者等信息都是存储在 Observation
链表当中的,只不过获取该 Observation
链表的路径不一样。
这里需要注意的是,如果监听者重复注册了多次监听,通知发送时将会收到多次通知。
2、addObserverForName:object:queue:usingBlock: 方法的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 - (id ) addObserverForName: (NSString *)name object: (id )object queue: (NSOperationQueue *)queue usingBlock: (GSNotificationBlock)block { GSNotificationObserver *observer = [[GSNotificationObserver alloc] initWithQueue: queue block: block]; [self addObserver: observer selector: @selector (didReceiveNotification:) name: name object: object]; return observer; } - (void ) didReceiveNotification: (NSNotification *)notif { if (_queue != nil ) { GSNotificationBlockOperation *op = [[GSNotificationBlockOperation alloc] initWithNotification: notif block: _block]; [_queue addOperation: op]; } else { CALL_BLOCK (_block, notif); } }
根据该方法源码可以看到该方法的实现就比较简单了:
创建一个临时监听者,并调用 addObserver:selector:name:object:
方法注册监听者
监听者收到通知时,在指定的队列 queue
中执行 block
。如果 queue
为空,直接在当前线程执行 block
。
3、postNotificationName:object:userInfo: 方法的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 - (void ) postNotificationName: (NSString *)name object: (id )object userInfo: (NSDictionary *)info { GSNotification *notification; notification = (id )NSAllocateObject (concrete, 0 , NSDefaultMallocZone ()); notification->_name = [name copyWithZone: [self zone]]; notification->_object = [object retain ]; notification->_info = [info retain ]; [self _postAndRelease: notification]; } - (void ) _postAndRelease: (NSNotification *)notification { count = GSIArrayCount(a); while (count-- > 0 ) { o = GSIArrayItemAtIndex(a, count).ext; if (o->next != 0 ){ [o->observer performSelector: o->selector withObject: notification]; } } RELEASE(notification); }
以上逻辑也比较简单:
创建 notification
对象
根据 name
& object
查找所有符合的 Observation
,并保存到数组中。
遍历数组,使用 performSelector:
逐一同步调用 sel
释放 notification
对象
三、NSNotification 与多线程 1、NSNotification 的发送和通知接收回调是在同一线程的 这点前面已经使用一个例子验证了,我们有时候是在子线程发出通知,想要在收到通知的时候在主线程更新 UI,除了在收到通知后主动切换到主线程外,还可以使用 addObserverForName:object:queue:usingBlock:
方法指定在主队列执行 block
的方式去实现。
其实 Apple 官方文档里还介绍了一种实现方案:使用 Mach Port 将通知转发到指定线程。假设在子线程发送通知,想在主线程处理通知,使用该方案的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 #import "ViewController.h" static NSString * const kNotificationName = @"kNotificationName" ;@interface ViewController () <NSMachPortDelegate >@property (nonatomic ) NSMutableArray *notifications;@property (nonatomic ) NSThread *notificationThread;@property (nonatomic ) NSLock *notificationLock;@property (nonatomic ) NSMachPort *notificationPort; @end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; self .notifications = [[NSMutableArray alloc] init]; self .notificationLock = [[NSLock alloc] init]; self .notificationThread = [NSThread currentThread]; self .notificationPort = [[NSMachPort alloc] init]; self .notificationPort.delegate = self ; [[NSRunLoop currentRunLoop] addPort:self .notificationPort forMode:(__bridge NSString *)kCFRunLoopCommonModes]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (processNotification:) name:kNotificationName object:nil ]; dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ NSLog (@"子发送通知:%@" , [NSThread currentThread]); [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationName object:nil userInfo:nil ]; }); } - (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] != _notificationThread) { [self .notificationLock lock]; [self .notifications addObject:notification]; [self .notificationLock unlock]; NSLog (@"需要转发,因为在非期望线程收到通知:%@" , [NSThread currentThread]); [self .notificationPort sendBeforeDate:[NSDate date] components:nil from:nil reserved:0 ]; } else { NSLog (@"在期望线程收到通知:%@" , [NSThread currentThread]); } } @end
打印结果:
1 2 3 子发送通知:<NSThread: 0x600002fb8a40>{number = 8, name = (null)} 需要转发,因为在非期望线程收到通知:<NSThread: 0x600002fb8a40>{number = 8, name = (null)} 在期望线程收到通知:<NSThread: 0x600002ff4900>{number = 1, name = main}
实现逻辑总结如下:
首先定义一个 notifications
数组,用于保存需要转发的 notification
在期望线程的 runloop 上添加一个 Mach Port 端口源
收到通知时判断是否是在期望线程收到的
如果是则直接处理;
如果不是,则将该 notification
保存到 notifications
数组中,并通过 Mach Port 端口发送一条 Message
由于 Mach Port 端口源是添加到期望线程 runloop 上的,所以收到 Mach Message 的 delegate
方法 handleMachMessage:
一定是在期望线程上被执行的
在 handleMachMessage:
中遍历 notifications
数组中所有 notification
,以 notification
为参数重新执行收到通知的处理方法即可
2、NSNotification 的发送和处理是同步的 NSNotification
的发送和处理是同步的,即 NSNotification
的所有监听者注册 sel
执行完毕后,才会执行发送 NSNotification
后面的后续逻辑,例如: 定义一个 TestObject
,用于用于监听与处理通知,并且在收到通知的 sel
中休眠 2 秒:
1 2 3 4 5 6 7 8 9 10 11 #import "TestObject.h" @implementation TestObject - (void )notificationAction:(NSNotification *)notification { NSLog (@"收到通知" ); sleep(2 ); NSLog (@"休眠 2 秒" ); } @end
然后创建三个 TestObject
的实例监听与处理通知:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 - (void )viewDidLoad { [super viewDidLoad]; _obj1 = [[TestObject alloc] init]; _obj2 = [[TestObject alloc] init]; _obj3 = [[TestObject alloc] init]; [[NSNotificationCenter defaultCenter] addObserver:_obj1 selector:@selector (notificationAction:) name:kNotificationName object:nil ]; [[NSNotificationCenter defaultCenter] addObserver:_obj2 selector:@selector (notificationAction:) name:kNotificationName object:nil ]; [[NSNotificationCenter defaultCenter] addObserver:_obj3 selector:@selector (notificationAction:) name:kNotificationName object:nil ]; } - (IBAction )btnClick:(id )sender { NSLog (@"点击了按钮,发送通知" ); [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationName object:nil ]; NSLog (@"按钮点击的最后一个打印" ); }
打印结果:
1 2 3 4 5 6 7 8 点击了按钮,发送通知 收到通知 休眠 2 秒 收到通知 休眠 2 秒 收到通知 休眠 2 秒 按钮点击的最后一个打印
根据打印结果可以看到,发送 Notification
会阻塞线程,直到所有监听者注册的 sel
执行完毕才会继续往下执行,即 Notification
的发送和处理是同步的。
3、异步发送通知 根据上面的例子可以知道,NSNotification
的发送和处理是同步的,那是否有办法异步发送通知呢?即发送 Notification
如何不阻塞线程?
可以借助前面提到的 NSNotificationQueue
来实现异步发送通知,实际上从线程的角度看并不是真正的异步发送,因为不涉及开启新线程,更应当称之为延时发送。
例如,在主线程发送通知,仍然在主线程收到通知,但是使发送通知不阻塞主线程。可以使用 NSNotificationQueue
将通知延迟到 runloop 空闲时发送,在上面例子基础上修改下通知发送方式即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 - (void )viewDidLoad { [super viewDidLoad]; _obj1 = [[TestObject alloc] init]; _obj2 = [[TestObject alloc] init]; _obj3 = [[TestObject alloc] init]; [[NSNotificationCenter defaultCenter] addObserver:_obj1 selector:@selector (notificationAction:) name:kNotificationName object:nil ]; [[NSNotificationCenter defaultCenter] addObserver:_obj2 selector:@selector (notificationAction:) name:kNotificationName object:nil ]; [[NSNotificationCenter defaultCenter] addObserver:_obj3 selector:@selector (notificationAction:) name:kNotificationName object:nil ]; } - (IBAction )btnClick:(id )sender { NSLog (@"点击了按钮,发送通知" ); NSNotification *notification = [NSNotification notificationWithName:kNotificationName object:nil ]; [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode ]]; NSLog (@"按钮点击的最后一个打印" ); }
打印结果:
1 2 3 4 5 6 7 8 点击了按钮,发送通知 按钮点击的最后一个打印 收到通知 休眠 2 秒 收到通知 休眠 2 秒 收到通知 休眠 2 秒
根据打印结果可以看到,该方式发送通知并没有阻塞线程,不需要等待所有监听者处理完通知即可执行后续逻辑。