关于我们日常所使用的NSNotification,其实有很多知识点和细节,推荐通读这篇文章:
总结如下:
一 基础内容
通知中心:
@property (class, readonly, strong) NSNotificationCenter *defaultCenter;
A 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
’spostNotification:
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 ofNSNotificationCenter
). TheNSNotificationQueue
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, anNSNotificationCenter
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];