NSNotification&NSNotificationQueue

关于我们日常所使用的NSNotification,其实有很多知识点和细节,推荐通读这篇文章:

苹果通知开发文档

总结如下:

一 基础内容

通知中心:

@property (class, readonly, strong) NSNotificationCenter *defaultCenter;

notification center manages the sending and receiving of notifications. It notifies all observers of notifications meeting specific criteria. The notification information is encapsulated in NSNotification objects. Client objects register themselves with the notification center as observers of specific notifications posted by other objects. When an event occurs, an object posts an appropriate notification to the notification center. The notification center dispatches a message to each registered observer, passing the notification as the sole argument.

通知中心管理着通知的发送和接收,观察者对象可以向通知中心注册去监听指定的通知,发送者可以向通知中心发送指定内容的通知。当事件发生时,发送者发送一个notification到通知中心,通知中心会向所有已经注册了的观察者派发该notification。

 

注册通知:

- (void)addObserver:(id)observer selector:(SEL)aSelector 
                                 name:(nullable NSNotificationName)aName 
                                 object:(nullable id)anObject;
//参数1 (id)observer 表示接受通知消息的观察者
//参数2 (SEL)aSelector 收到通知后调用的方法
//参数3 (nullable NSNotificationName)aName 监听的通知名字
//参数4 (nullable id)anObject 监听发送给某个对象的通知
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name 
                                    object:(nullable id)obj 
                                    queue:(nullable NSOperationQueue *)queue 
                                    usingBlock:(void (^)(NSNotification *note))block;
//这个方法可以直接指定收到通知时,block执行的线程
//参数1 (nullable NSNotificationName)name 通知的名字
//参数2 (nullable id)obj 监听发给某一个对象的所有通知
//参数3 (nullable NSOperationQueue *)queue 收到通知的执行block的线程 
//参数4 (void (^)(NSNotification *note))block 收到通知时执行的block
//这个方法会创建一个匿名对象作为观察者(同时返回该对象),这个匿名对象会在指定队列(queue)上去执行block。

注意的点:

1.通知的名称不要直接使用字面量,要使用变量(文档中提到),但是感觉用常量好。

static NSString *const 常量名 = @"内容";

It is possible for an observer to register to receive more than one message for the same notification. In such a case, the observer will receive all messages it is registered to receive for the notification, but the order in which it receives them cannot be determined.

2.一个观察者可能会收到多个已监听的同名通知,他们到达的先后顺序时无法确定的,所以他们执行回调的先后顺序也是无法确定的。

3.接收通知的参数 (id)observer 不能为空。//Null passed to a callee that requires a non-null argument

4.监听通知方法中的 name:(nullable NSNotificationName)aName  object:(nullable id)anObject这两个参数,如果name传入空,会监听发送给object对象的所有通知,如果object对象也为空,会监听系统产生的所有通知。

5.如果object为nil,则会接收所有name的通知;只要是object存在,即使name也存在,也只接收发送给object的通知。

 

发送通知:

//NSNotification初始化方法
+ (instancetype)notificationWithName:(NSNotificationName)aName 
                                      object:(nullable id)anObject;
+ (instancetype)notificationWithName:(NSNotificationName)aName 
                                      object:(nullable id)anObject 
                                      userInfo:(nullable NSDictionary *)aUserInfo;
- (instancetype)initWithName:(NSNotificationName)name 
                             object:(nullable id)object 
                             userInfo:(nullable NSDictionary *)userInfo;

//发送通知
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName 
                             object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName 
                             object:(nullable id)anObject 
                             userInfo:(nullable NSDictionary *)aUserInfo;
//参数 (NSNotification *)notification  通知对象
//参数 (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;

 

二 更多

1.接收通知一定是在子线程吗?

In a multithreaded application, notifications are always delivered in the thread in which the notification was posted, which may not be the same thread in which an observer registered itself.

接收通知执行的回调 会在 发送该通知的线程中执行,所以如果是主线程发送的通知,执行回调也会在主线程;子线程发送的通知,执行的回调会在子线程。

大家可以自己做实验,代码比较简单,就不贴出来了。

2.发送通知是同步还是异步的?

A notification center delivers notifications to observers synchronously. In other words, when posting a notification, control does not return to the poster until all observers have received and processed the notification. 

Using the NSNotificationCenter’s postNotification: method and its variants, you can post a notification to a notification center. However, the invocation of the method is synchronous: before the posting object can resume its thread of execution, it must wait until the notification center dispatches the notification to all observers and returns. 

通知的派发过程是 同步 的,当我们在主线程发送一个通知时,主线程会等待 通知发送到每一个观察者对象(已经在通知中心注册了当前通知的监听) 并且 等待这些观察者对象处理完通知之后 才会继续执行下去。也就是说有阻塞线程的风险。

3.如何异步发送通知

使用通知队列(NSNotificationQueue)

NSNotificationQueue objects, or simply, notification queues, act as buffers for notification centers (instances of NSNotificationCenter). The NSNotificationQueue class contributes two important features to the Foundation Kit’s notification mechanism: the coalescing of notifications and asynchronous posting.

我们可以把通知队列看成是通向NSNotificationCenter的缓冲区,通知队列主要有两个特性

1.通知合并;2.异步发送通知。

A notification queue, on the other hand, maintains notifications (instances of NSNotification) generally in a First In First Out (FIFO) order. When a notification rises to the front of the queue, the queue posts it to the notification center, which in turn dispatches the notification to all objects registered as observers.

通知队列用"先进先出"的方式来维持一个通知池,当一个通知到达队列的前端时,就会被发送至通知中心。

当通过这个方法发送通知时,不一定会立即发送通知,而是先将通知加入队列中,然后按照设定的参数规则进行发送。

@property (class, readonly, strong) NSNotificationQueue *defaultQueue;
- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter;

//添加通知到队列中
- (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:(NSPostingStyle)postingStyle

typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1,
    //Posting When Idle 
    //A notification queued with the NSPostWhenIdle style is posted only when the
    //run loop is in a wait state.   
    //runloop处于即将进入休眠状态时会发送通知

    NSPostASAP = 2,
    //Posting As Soon As Possible
    //Any notification queued with the NSPostASAP style is posted to the
    //notification center when the current iteration of the run loop completes,
    //assuming the current run loop mode matches the requested mode.
    //当 当前runloop的mode和参数的mode一致时 当轮runloop循环完成后发送通知

    NSPostNow = 3
    //Posting Immediately
    //A notification queued with NSPostNow is posted immediately after coalescing
    //to the notification center. You queue a notification with NSPostNow (or post one
    //with postNotification:) when you do not require asynchronous calling behavior.
    //立即发送通知
};

参数:coalesceMask:(NSNotificationCoalescing)coalesceMask

In some situations, you may want to post a notification if a given event occurs at least once, but you want to post no more than one notification even if the event occurs multiple times. 

如果我们希望一个事件发生后会发出至少一个通知,但是当该事件多次发生(一个runloop的运行时间内),不会再发出通知。

In some situations it may be possible to simply set a Boolean flag (whether an instance variable of an object or a global variable) to denote that an event has occurred and to suppress posting of further notifications until the flag is cleared. If this is not possible, however, in this situation you cannot directly use NSNotificationCenter since its behavior is synchronous—notifications are posted before returning, thus there is no opportunity for "ignoring” duplicate notifications; moreover, an NSNotificationCenter instance has no way of knowing whether more notifications are coming or not.

Coalescing is a process that removes from a queue notifications that are similar in some way to a notification that was queued earlier. You indicate the criteria for similarity by specifying one or more of the following constants 

聚结(Coalescing)可以移除队列中早先存在的具有指定相似内容的通知。就是说在一个runloop的循环时间内,多次发送通知后,实际只会发送一条通知。

typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
    NSNotificationNoCoalescing = 0,//不聚合
    NSNotificationCoalescingOnName = 1,//按名字聚合
    NSNotificationCoalescingOnSender = 2//按发送者聚合
};

三 发送通知到指定线程

you must capture the notifications as they are delivered on the default thread and redirect them to the appropriate thread. 

刚才的说明我们知道,通知的执行回调会在发送通知的线程上执行,那么我们如何让执行回调的线程压根不让发送通知的线程去处理呢?我们需要捕获在默认线程上派发的通知,然后将它重定向到合适的线程上。

//苹果官方示例

@interface MyThreadedClass: NSObject
/* Threaded notification support. */
@property NSMutableArray *notifications;
@property NSThread *notificationThread;
@property NSLock *notificationLock;
@property NSMachPort *notificationPort;
 
- (void) setUpThreadingSupport;
- (void) handleMachMessage:(void *)msg;
- (void) processNotification:(NSNotification *)notification;
@end
- (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:(NSString __bridge *)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;
    }
[self setupThreadingSupport];
[[NSNotificationCenter defaultCenter]
        addObserver:self
        selector:@selector(processNotification:)
        name:@"NotificationName"
        object:nil];

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值