ios 的 notification 在多线程的状况下,线程的管理很是很差控制。这个怎么理解呢?html
按照官方文档的说法就是,无论你在哪一个线程注册了 observer,notification 在哪一个线程 post,那么它就将在哪一个线程接收,这个意思用代码表示,效果以下:ios
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"current thread = %@", [NSThread currentThread]); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:POST_NOTIFICATION object:nil]; } - (void)viewDidAppear:(BOOL)animated { [self postNotificationInBackground]; } - (void)postNotificationInBackground { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:POST_NOTIFICATION object:nil userInfo:nil]; }); } - (void)handleNotification:(NSNotification *)notification { NSLog(@"current thread = %@", [NSThread currentThread]); }
输出以下:git
2016-07-02 11:20:56.683 Test[31784:3602420] current thread = <NSThread: 0x7f8548405250>{number = 1, name = main} 2016-07-02 11:20:56.684 Test[31784:3602420] viewWillAppear: ViewController 2016-07-02 11:20:56.689 Test[31784:3602469] current thread = <NSThread: 0x7f854845b790>{number = 2, name = (null)}
也就是说,尽管我在主线程注册了 observer,可是因为我在子线程 post 了消息,那么 handleNotification 响应函数也会在子线程处理。这样一来就会给咱们带来困扰,由于 notification 的响应函数执行线程将变得不肯定,并且不少操做如 UI 操做,咱们是须要在主线程进行的。github
怎么解决这个问题呢?api
一个很土的方法就在在 handleNotification 里面,强制切换线程,如:数组
- (void)handleNotification:(NSNotification *)notification { dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"current thread = %@", [NSThread currentThread]); }); }
每个响应函数都强制切换线程。这样带来的问题就是每一处理代码你都得这样作,对于开发者而言负担太大,显然是下下策。多线程
其实解决思路和上面的差很少,不过实现的方式更优雅一点,这个方案在 apple 的官方文档中有详细介绍,它的思路翻译过来就是:重定向通知的一种的实现思路是使用一个通知队列(注意,不是 NSNotificationQueue 对象,而是一个数组)去记录全部的被抛向非预期线程里面的通知,而后将它们重定向到预期线程。这种方案使咱们仍然是像日常同样去注册一个通知的观察者,当接收到 Notification 的时候,先判断 post 出来的这个 Notification 的线程是否是咱们所指望的线程,若是不是,则将这个 Notification 存储到咱们自定义的队列中,并发送一个信号( signal )到指望的线程中,来告诉这个线程须要处理一个 Notification 。指定的线程在收到信号后,将 Notification 从队列中移除,并进行处理。并发
/* Threaded notification support. */ @property (nonatomic) NSMutableArray *notifications; // 通知队列 @property (nonatomic) NSThread *notificationThread; // 预想的处理通知的线程 @property (nonatomic) NSLock *notificationLock; // 用于对通知队列加锁的锁对象,避免线程冲突 @property (nonatomic) NSMachPort *notificationPort; // 用于向预想的处理线程发送信号的通讯端口 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"current thread = %@", [NSThread currentThread]); [self setUpThreadingSupport]; // 往当前线程的run loop添加端口源 // 当Mach消息到达而接收线程的run loop没有运行时,则内核会保存这条消息,直到下一次进入run loop [[NSRunLoop currentRunLoop] addPort:self.notificationPort forMode:(__bridge NSString *)kCFRunLoopCommonModes]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:POST_NOTIFICATION object:nil]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:POST_NOTIFICATION object:nil userInfo:nil]; }); } - (void) setUpThreadingSupport { if (self.notifications) { return; } self.notifications = [[NSMutableArray alloc] init]; self.notificationLock = [[NSLock alloc] init]; self.notificationThread = [NSThread currentThread]; self.notificationPort = [[NSMachPort alloc] init]; [self.notificationPort setDelegate:self]; [[NSRunLoop currentRunLoop] addPort:self.notificationPort forMode:(__bridge 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] != _notificationThread) { // Forward the notification to the correct thread. [self.notificationLock lock]; [self.notifications addObject:notification]; [self.notificationLock unlock]; [self.notificationPort sendBeforeDate:[NSDate date] components:nil from:nil reserved:0]; } else { // Process the notification here; NSLog(@"current thread = %@", [NSThread currentThread]); NSLog(@"process notification"); } } }
可是这种方案有明显额缺陷,官方文档也对其进行了说明,归结起来有两点:app
全部的通知的处理都要通过 processNotification 函数进行处理。async
全部的接听对象都要提供相应的 NSMachPort 对象,进行消息转发。
正是因为存在这样的缺陷,所以官方文档并不建议直接这样使用,而是鼓励开发者去继承NSNoticationCenter 或者本身去提供一个单独的类进行线程的维护。
为了顺应语法的变化,apple 从 ios4 以后提供了带有 block 的 NSNotification。使用方式以下:
- (id<NSObject>)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block
这里说明几点
观察者就是当前对象
queue 定义了 block 执行的线程,nil 则表示 block 的执行线程和发通知在同一个线程
block 就是相应通知的处理函数
这个 API 已经可以让咱们方便的控制通知的线程切换。可是,这里有个问题须要注意。就是其 remove 操做。
首先回忆一下咱们原来的 NSNotification 的 remove 方式,见以下代码:
- (void)removeObservers { [[NSNotificationCenter defaultCenter] removeObserver:self name:POST_NOTIFICATION object:nil]; }
须要指定 observer 以及 name。可是带 block 方式的 remove 便不能像上面这样处理了。其方式以下:
- (void)removeObservers { if(_observer){ [[NSNotificationCenter defaultCenter] removeObserver:_observer]; } }
其中 _observer 是 addObserverForName 方式的 api 返回观察者对象。这也就意味着,你须要为每个观察者记录一个成员对象,而后在 remove 的时候依次删除。试想一下,你若是须要 10 个观察者,则须要记录 10 个成员对象,这个想一想就是很麻烦,并且它还不可以方便的指定 observer 。所以,理想的作法就是本身再作一层封装,将这些细节封装起来。
git 上有一个想要解决上述问题的开源代码,其使用方式以下:
+ (void)observeName:(NSString *)name owner:(id)owner dispatchQueue:(dispatch_queue_t)dispatchQueue block:(LRNotificationObserverBlock)block;
它可以方便的控制线程切换,并且它还能作到 owner dealloc 的时候,自动 remove observer。好比咱们不少时候在 viewDidLoad 的时候addObserver,而后还须要重载 dealloc,在里面调用 removeObserver,这个开源方案,帮咱们省去了再去dealloc 显示 remove 的额外工做。可是若是你想显式的调用 remove,就比较麻烦了(好比有时候,咱们在viewWillAppear 添加了 observer,须要在 viewWillDisAppear 移除 observer),它相似官方的解决方案,须要你用成员变量,将 observer 一个个保存下来,而后在 remove 的地方移除。
为了解决上面的问题,所以决定从新写一个 Notification 的管理类,GYNotificationCenter 想要达到的效果有两个
可以方便的控制线程切换
可以方便的remove observer
- (void)addObserver:(nonnull id)observer name:(nonnull NSString *)aName dispatchQueue:(nullable dispatch_queue_t)disPatchQueue block:(nonnull GYNotificatioObserverBlock)block;
咱们提供了和官方 api 几乎同样的调用方法,支持传入 dispatchQueue 实现线程切换控制,同时可以以 block 的方式处理消息响应,并且支持在 observer dealloc 的时候,自动调用 observer 的 remove 操做。同时还提供了和原生同样的显式调用 remove 的操做,方便收到调用 remove .
- (void)removerObserver:(nonnull id)observer name:(nonnull NSString *)anName object:(nullable id)anObject; - (void)removerObserver:(nonnull id)observer;
可以方便的手动调用 remove 操做。
GYNotificaionCenter 借鉴了官方的线程重定向 以及 LRNotificationObserver 的一些方案。在 addObserver 的时候,生成了一个和 observer 关联的 GYNotificationOberverIdentifer 对象,这个对象记录了传入的 block 、name 的数据,而后对这个对象依据传入的 name 注册观察者。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:anName object:object];
当收到通知的时候,在 handleNotification 里面执行传入的 block,回调的外面去。
- (void)handleNotification:(NSNotification *)notification { if (self.dispatchQueue) { dispatch_async(self.dispatchQueue, ^{ if (self.block) { self.block(notification); } }); } else { self.block(notification); } }
GYNotificationOberverIdentifer 对象放入 GYNotificationOberverIdentifersContainer 对象中进行统一管理。
- (void)addNotificationOberverIdentifer:(GYNotificationOberverIdentifer *)identifier { NSAssert(identifier,@"identifier is nil"); if (identifier) { NotificationPerformLocked(^{ [self modifyContainer:^(NSMutableDictionary *notificationOberverIdentifersDic) { //不重复add observer if (![notificationOberverIdentifersDic objectForKey:identifier.name]) { [notificationOberverIdentifersDic setObject:identifier forKey:identifier.name]; } }]; }); } }
这个对象也和 observer 关联。因为其和 observer 是关联的,所以当 observer 释放的时候,GYNotificationOberverIdentifer 也会释放,所以,也就能在 GYNotificationOberverIdentifer 的 dealloc 里面调用 remove 操做移除通知注册从而实现自动 remove。
同时因为 GYNotificationOberverIdentifersContainer 里面保留了全部的 Identifer 对象,所以也就可以方便的根据 name 进行 remove 了。
- (void)removeObserverWithName:(NSString *)name { if (name) { NotificationPerformLocked(^{ [self modifyContainer:^(NSMutableDictionary *notificationOberverIdentifersDic) { if ([notificationOberverIdentifersDic objectForKey:name]) { GYNotificationOberverIdentifer *identifier = (GYNotificationOberverIdentifer *)[notificationOberverIdentifersDic objectForKey:name]; [identifier stopObserver]; [notificationOberverIdentifersDic removeObjectForKey:name]; } }]; }); } }