NSNotification 线程管理以及自动注销开源方案

背景

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 或者本身去提供一个单独的类进行线程的维护。

block 方式的 NSNotification

为了顺应语法的变化,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 。所以,理想的作法就是本身再作一层封装,将这些细节封装起来。

LRNotificationObserver

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 的地方移除。

GYNotificationCenter

为了解决上面的问题,所以决定从新写一个 Notification 的管理类,GYNotificationCenter 想要达到的效果有两个

  1. 可以方便的控制线程切换

  2. 可以方便的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];
                }
            }];

        });
    }
}
相关文章
相关标签/搜索