本文从源码角度对iOS中的通知进行了解析,并对通知中心的一些特性进行了相应的解读。
NSNotification理所当然要包含通知name、object,且使用userInfo用于传递参数。
open class NSNotification: NSObject, NSCopying, NSCoding { public struct Name : RawRepresentable, Equatable, Hashable { public private(set) var rawValue: String public init(_ rawValue: String) { self.rawValue = rawValue } public init(rawValue: String) { self.rawValue = rawValue } } private(set) open var name: Name private(set) open var object: Any? private(set) open var userInfo: [AnyHashable : Any]? } 复制代码
这里封装了一个结构体Name,而非直接使用字符串。所以,我们通常使用的话,需要这样写 ***NotificationCenter.default.post(name: NSNotification.Name(rawValue: kNotificationCLLocationDidUpdated), object: nil)***。
基于Swift的特点,我们可以对Swift项目中的相关Notification使用进行一些优雅的改进。
强烈建议采用Alamofire中这样的写法:
extension Notification.Name { /// Used as a namespace for all `URLSessionTask` related notifications. public struct Task { /// Posted when a `URLSessionTask` is resumed. The notification `object` contains the resumed `URLSessionTask`. public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume") /// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`. public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend") /// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`. public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel") /// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`. public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete") } } 复制代码
则使用方式就很简单了,且通过类似 Notification.Name.Task 这样的写法来进行了业务的区分。
NotificationCenter.default.post( name: Notification.Name.Task.DidComplete, object: strongSelf, userInfo: userInfo ) 复制代码
NSNotificationReceiver用于封装通知的基本结构,name、block、sender都包含在里边。这里的name使用的其实就是Notification.Name对象,sender即为发送通知的对象。
private class NSNotificationReceiver : NSObject { fileprivate var name: Notification.Name? fileprivate var block: ((Notification) -> Void)? fileprivate var sender: AnyObject? fileprivate var queue: OperationQueue? } 复制代码
NotificationCenter中使用_observers来存储NSNotificationReceiver对象。
private var _observers: [AnyHashable /* Notification.Name */ : [ObjectIdentifier /* object */ : [ObjectIdentifier /* notification receiver */ : NSNotificationReceiver]]] 复制代码
简化一下就是
private var _observers: [AnyHashable : [ObjectIdentifier : [ObjectIdentifier : NSNotificationReceiver]]] 复制代码
为啥要使用这么复杂的结构?而不是直接使用 [Name: [NSNotificationReceiver]]这样的结构,原因在于区分通知的不仅仅是name,还有发送对象即object。一个通知名(String)可以对应于多个通知Receiver。
_observers的key是一个可hash的对象,value是一个字典。该value字典的key是ObjectIdentifier,而value则是[ObjectIdentifier : NSNotificationReceiver],又是一个字典。
后续会讲到ObjectIdentifier,这里仅简单理解为唯一标记一个对象(通知的sender)即可。
addObserver将通知加到通知中心。
open func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol { let newObserver = NSNotificationReceiver() newObserver.name = name newObserver.block = block newObserver.sender = __SwiftValue.store(obj) newObserver.queue = queue let notificationNameIdentifier: AnyHashable = name.map({ AnyHashable($0) }) ?? _nilHashable let senderIdentifier: ObjectIdentifier = newObserver.sender.map({ ObjectIdentifier($0) }) ?? _nilIdentifier let receiverIdentifier: ObjectIdentifier = ObjectIdentifier(newObserver) _observersLock.synchronized({ _observers[notificationNameIdentifier, default: [:]][senderIdentifier, default: [:]][receiverIdentifier] = newObserver }) return newObserver } 复制代码
先封装一个NSNotificationReceiver对象,注意这里对发送通知的对象obj使用了__SwiftValue.store(obj)操作,存到了sender中。
将name转换为notificationNameIdentifier,将newObserver.sender转为为ObjectIdentifier对象senderIdentifier。
注意为nil的时候,分别转换成了_nilHashable和_nilIdentifier,即:
private lazy var _nilIdentifier: ObjectIdentifier = ObjectIdentifier(_observersLock) private lazy var _nilHashable: AnyHashable = AnyHashable(_nilIdentifier) 复制代码
开发者文档是这样解释的:
name: The name of the notification for which to register the observer; that is, only notifications with this name are used to add the block to the operation queue. If you pass nil, the notification center doesn’t use a notification’s name to decide whether to add the block to the operation queue.
obj:The object whose notifications the observer wants to receive; that is, only notifications sent by this sender are delivered to the observer. If you pass nil, the notification center doesn’t use a notification’s sender to decide whether to deliver it to the observer.
name用于唯一标识一个通知,obj为发送通知的对象。如果name和obj都为nil,则addObserver会注册一个observer,对所有的通知进行响应。如:
[[NSNotificationCenter defaultCenter] addObserverForName:nil object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { NSLog(@"block 3 %@", note.name); sleep(2); }]; 复制代码
以上代码会对所有通知进行响应,打印结果如下:
2020-04-14 23:11:40.027720+0800 DemoRunloop[15128:4831118] block 3 NSThreadWillExitNotification 2020-04-14 23:11:41.188560+0800 DemoRunloop[15128:4830986] block 3 1234567 2020-04-14 23:11:43.189717+0800 DemoRunloop[15128:4830986] block 3 UIApplicationDidFinishLaunchingNotification 2020-04-14 23:11:45.171794+0800 DemoRunloop[15128:4831116] block 3 NSThreadWillExitNotification 2020-04-14 23:11:45.190932+0800 DemoRunloop[15128:4830986] block 3 UIApplicationSuspendedNotification 2020-04-14 23:11:47.191469+0800 DemoRunloop[15128:4830986] block 3 _UIWindowContentWillRotateNotification 2020-04-14 23:11:47.244987+0800 DemoRunloop[15128:4831122] block 3 NSThreadWillExitNotification 2020-04-14 23:11:49.192743+0800 DemoRunloop[15128:4830986] block 3 UIDeviceOrientationDidChangeNotification 2020-04-14 23:11:51.194071+0800 DemoRunloop[15128:4830986] block 3 _UIApplicationDidRemoveDeactivationReasonNotification 复制代码
继续往下看,就是将封装好的NSNotificationReceiver对象存储到_observers中。
_observers[notificationNameIdentifier, default: [:]][senderIdentifier, default: [:]][receiverIdentifier] = newObserver 复制代码
对照着定义:
private var _observers: [AnyHashable : [ObjectIdentifier : [ObjectIdentifier : NSNotificationReceiver]]] 复制代码
Swift中的Dictionary可以使用default来指定默认值。
removeObserver即根据_observers执行相应的移除操作。
open func removeObserver(_ observer: Any, name aName: NSNotification.Name?, object: Any?) { guard let observer = observer as? NSNotificationReceiver, // These 2 parameters would only be useful for removing notifications added by `addObserver:selector:name:object:` aName == nil || observer.name == aName, object == nil || observer.sender === __SwiftValue.store(object) else { return } let notificationNameIdentifier: AnyHashable = observer.name.map { AnyHashable($0) } ?? _nilHashable let senderIdentifier: ObjectIdentifier = observer.sender.map { ObjectIdentifier($0) } ?? _nilIdentifier let receiverIdentifier: ObjectIdentifier = ObjectIdentifier(observer) _observersLock.synchronized({ _observers[notificationNameIdentifier]?[senderIdentifier]?.removeValue(forKey: receiverIdentifier) if _observers[notificationNameIdentifier]?[senderIdentifier]?.count == 0 { _observers[notificationNameIdentifier]?.removeValue(forKey: senderIdentifier) } }) } 复制代码
postNotification的目的很简单,就是遍历存储的所有NSNotificationReceiver对象,找到符合条件的,将通知发送到Receiver即可。
open func post(name aName: NSNotification.Name, object anObject: Any?, userInfo aUserInfo: [AnyHashable : Any]? = nil) { let notification = Notification(name: aName, object: anObject, userInfo: aUserInfo) post(notification) } 复制代码
先构建一个Notification对象,然后调用post函数:
open func post(_ notification: Notification) { let notificationNameIdentifier: AnyHashable = AnyHashable(notification.name) // 转换成ObjectIdentifier let senderIdentifier: ObjectIdentifier? = notification.object.map({ ObjectIdentifier(__SwiftValue.store($0)) }) let sendTo: [Dictionary<ObjectIdentifier, NSNotificationReceiver>.Values] = _observersLock.synchronized({ var retVal = [Dictionary<ObjectIdentifier, NSNotificationReceiver>.Values]() (_observers[_nilHashable]?[_nilIdentifier]?.values).map({ retVal.append($0) }) senderIdentifier.flatMap({ _observers[_nilHashable]?[$0]?.values }).map({ retVal.append($0) }) (_observers[notificationNameIdentifier]?[_nilIdentifier]?.values).map({ retVal.append($0) }) senderIdentifier.flatMap({ _observers[notificationNameIdentifier]?[$0]?.values}).map({ retVal.append($0) }) return retVal }) sendTo.forEach { observers in observers.forEach { observer in guard let block = observer.block else { return } if let queue = observer.queue, queue != OperationQueue.current { queue.addOperation { block(notification) } queue.waitUntilAllOperationsAreFinished() } else { block(notification) } } } } 复制代码
原理其实很简单:根据通知name和sender来查找对应的Receiver,然后调用其响应即可。然而,查找的过程看着相当累。。。
这句代码 let senderIdentifier: ObjectIdentifier? = notification.object.map({ ObjectIdentifier(__SwiftValue.store($0)) }) 要与addObserver中的 newObserver.sender = __SwiftValue.store(obj) 结合起来看,就是根据Notification的object对象(也就是通知的sender),转换为一个ObjectIdentifier唯一表示。并且,再一次强调一下 _observers 这个变态的字典。
private var _observers: [AnyHashable : [ObjectIdentifier : [ObjectIdentifier : NSNotificationReceiver]]] 复制代码
下边的一句更变态,sendTo实际上是一个数组,其值就是字典的Values,即sendTo是NSNotificationReceiver组成的数组。添加一些注释:
let sendTo: [Dictionary<ObjectIdentifier, NSNotificationReceiver>.Values] = _observersLock.synchronized({ // 初始化一个空数组 var retVal = [Dictionary<ObjectIdentifier, NSNotificationReceiver>.Values]() // name为nil的observer,通过_nilHashable为key来查找,即 _observers[_nilHashable]? 结果为 [ObjectIdentifier : [ObjectIdentifier : NSNotificationReceiver]] // name为nil且object为nil的observer,通过 _nilIdentifier为key来查找,即 _observers[_nilHashable]?[_nilIdentifier]? 结果为 [ObjectIdentifier : NSNotificationReceiver] // 不区分name和object的observer,都会收到该通知 (_observers[_nilHashable]?[_nilIdentifier]?.values).map({ retVal.append($0) }) // 没有name的observer(即_observers[_nilHashable]?),若ObjectIdentifier为senderIdentifier,则会收到该通知 senderIdentifier.flatMap({ _observers[_nilHashable]?[$0]?.values }).map({ retVal.append($0) }) // name符合的observer(即_observers[notificationNameIdentifier]?),若object为nil(即_observers[notificationNameIdentifier]?[_nilIdentifier]?),则会受到该通知 (_observers[notificationNameIdentifier]?[_nilIdentifier]?.values).map({ retVal.append($0) }) // name和object均符合的observer,则会收到该通知 senderIdentifier.flatMap({ _observers[notificationNameIdentifier]?[$0]?.values}).map({ retVal.append($0) }) return retVal }) 复制代码
使用Swift的高阶函数,写了这么大一堆代码,实际上就是为了过滤出满足条件的NSNotificationReceiver而已:
我个人觉得这种写法给我带来了不少困惑,直接一个for循环加上判断不就解决了么?想更加Swift的话,使用 _observers.filter({ 筛选出符合条件的observer }) 不就可以了么?何必如此复杂。
总之,得到了NSNotificationReceiver数组sendTo之后,就是执行observer任务的时候了:
sendTo.forEach { observers in observers.forEach { observer in guard let block = observer.block else { return } if let queue = observer.queue, queue != OperationQueue.current { queue.addOperation { block(notification) } queue.waitUntilAllOperationsAreFinished() } else { block(notification) } } } 复制代码
如果observer指定了queue,且与当前post的queue一致,则将任务加到queue,调用 queue.waitUntilAllOperationsAreFinished() 会将当前的任务执行完毕,才会对下一个observer任务执行addOperation。
如果没有指定queue,则直接执行observer的任务。所以observer的任务执行的线程,依然与当前post的保持一致。这也是iOS中通知中心非常重要的一点!!!
/// A unique identifier for a class instance or metatype. /// /// In Swift, only class instances and metatypes have unique identities. There /// is no notion of identity for structs, enums, functions, or tuples. 复制代码
用于唯一表示一个类的实例或者metatype。
// TODO: Making this a SwiftObject subclass would let us use Swift refcounting, // but we would need to be able to emit __SwiftValue's Objective-C class object // with the Swift destructor pointer prefixed before it. // // The layout of `__SwiftValue` is: // - object header, // - `SwiftValueHeader` instance, // - the payload, tail-allocated (the Swift value contained in this box). // // NOTE: older runtimes called this _SwiftValue. The two must // coexist, so it was renamed. The old name must not be used in the new // runtime. @interface __SwiftValue : NSObject <NSCopying> - (id)copyWithZone:(NSZone *)zone; @end 复制代码
关于__SwiftValue,可以参考 奇怪的AnyObject和背后的SwiftValue。
鉴于Objective-C版本与Swift版本完全不一样,这里也一并进行解析。这里采用GNUStep的源码。
结构基本类似:
@interface NSNotification : NSObject <NSCopying, NSCoding> - (NSString*) name; - (id) object; - (NSDictionary*) userInfo; @end @implementation NSNotification static Class abstractClass = 0; static Class concreteClass = 0; + (void) initialize { if (concreteClass == 0) { abstractClass = [NSNotification class]; concreteClass = [GSNotification class]; } } + (NSNotification*) notificationWithName: (NSString*)name object: (id)object userInfo: (NSDictionary*)info { return [concreteClass notificationWithName: name object: object userInfo: info]; } @end 复制代码
看GSNotification:
/** * Concrete class implementing NSNotification. */ @interface GSNotification : NSNotification { @public NSString *_name; id _object; NSDictionary *_info; } @end @implementation GSNotification + (NSNotification*) notificationWithName: (NSString*)name object: (id)object userInfo: (NSDictionary*)info { GSNotification *n; n = (GSNotification*)NSAllocateObject(self, 0, NSDefaultMallocZone()); n->_name = [name copyWithZone: [self zone]]; n->_object = TEST_RETAIN(object); n->_info = TEST_RETAIN(info); return AUTORELEASE(n); } @end 复制代码
初始化的通知对象是GSNotification,包含了name、object、info。
Observation用于封装观察者。
typedef struct Obs { id observer; /* Object to receive message. */ SEL selector; /* Method selector. */ struct Obs *next; /* Next item in linked list. */ int retained; /* Retain count for structure. */ struct NCTbl *link; /* Pointer back to chunk table */ } Observation; 复制代码
Observation封装了observer、selector,通过next指针构成了一个链表结构。相当于Swift版本的NSNotificationReceiver。
NCTable存储了所有的Observation对象。
typedef struct NCTbl { Observation *wildcard; /* Get ALL messages. */ GSIMapTable nameless; /* Get messages for any name. */ GSIMapTable named; /* Getting named messages only. */ unsigned lockCount; /* Count recursive operations. */ NSRecursiveLock *_lock; /* Lock out other threads. */ Observation *freeList; Observation **chunks; unsigned numChunks; GSIMapTable cache[CACHESIZE]; unsigned short chunkIndex; unsigned short cacheIndex; } NCTable; 复制代码
nameless是负责没有name的通知,而named则是有name的。
freeList、chunks、cache用于查找observer时的缓存机制,提高查找效率。
@interface GSNotificationObserver : NSObject { NSOperationQueue *_queue; GSNotificationBlock _block; } @end 复制代码
GSNotificationObserver对象包含了block。如果使用了 addObserverForName:object:queue:usingBlock: 接口,会先封装一个GSNotificationObserver对象,然后再调用 addObserver:selector:name:object: 接口,传入的selector即为 @selector(didReceiveNotification:) 。
- (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; } 复制代码
didReceiveNotification如下,
- (void) didReceiveNotification: (NSNotification *)notif { if (_queue != nil) { GSNotificationBlockOperation *op = [[GSNotificationBlockOperation alloc] initWithNotification: notif block: _block]; [_queue addOperation: op]; } else { CALL_BLOCK(_block, notif); } } 复制代码
如果指定了queue,则将NSNotification对象和GSNotificationObserver对象的_block包装成了GSNotificationBlockOperation,然后加入到queue中,由queue来调度执行。如果未指定queue,则直接 CALL_BLOCK(_block, notif); 来调用。
GSNotificationBlockOperation的任务,其实也就是 ***CALL_BLOCK(_block, _notification);***。
@implementation GSNotificationBlockOperation - (id) initWithNotification: (NSNotification *)notif block: (GSNotificationBlock)block { self = [super init]; if (self == nil) return nil; ASSIGN(_notification, notif); _block = Block_copy(block); return self; } - (void) main { CALL_BLOCK(_block, _notification); } @end 复制代码
CALL_BLOCK即为调用block,传入指定参数而已。
/** * Calls a block. Works irrespective of whether the compiler supports blocks. */ #define CALL_BLOCK(block, args, ...) block(args, ## __VA_ARGS__) 复制代码
- (void) addObserver: (id)observer selector: (SEL)selector name: (NSString*)name object: (id)object { Observation *list; Observation *o; GSIMapTable m; GSIMapNode n; if (observer == nil) [NSException raise: NSInvalidArgumentException format: @"Nil observer passed to addObserver ..."]; if (selector == 0) [NSException raise: NSInvalidArgumentException format: @"Null selector passed to addObserver ..."]; if ([observer respondsToSelector: selector] == NO) { [NSException raise: NSInvalidArgumentException format: @"[%@-%@] Observer '%@' does not respond to selector '%@'", NSStringFromClass([self class]), NSStringFromSelector(_cmd), observer, NSStringFromSelector(selector)]; } lockNCTable(TABLE); o = obsNew(TABLE, selector, observer); /* * Record the Observation in one of the linked lists. * * NB. It is possible to register an observer for a notification more than * once - in which case, the observer will receive multiple messages when * the notification is posted... odd, but the MacOS-X docs specify this. */ if (name) { /* * Locate the map table for this name - create it if not present. */ n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name); if (n == 0) { m = mapNew(TABLE); /* * As this is the first observation for the given name, we take a * copy of the name so it cannot be mutated while in the map. */ name = [name copyWithZone: NSDefaultMallocZone()]; GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m); GS_CONSUMED(name) } else { m = (GSIMapTable)n->value.ptr; } /* * Add the observation to the list for the correct object. */ 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 { list = (Observation*)n->value.ptr; o->next = list->next; list->next = o; } } else { o->next = WILDCARD; WILDCARD = o; } unlockNCTable(TABLE); } 复制代码
通过obsNew函数,将observer、selector组成一个Observation对象,存储到_table中。根据name和object是否为nil的情况,将不同的observer对象,分别存储到不同的地方(WILDCARD、NAMELESS、NAMED)。这一点与前边Swift的实现一致。
#define TABLE ((NCTable*)_table) #define WILDCARD (TABLE->wildcard) #define NAMELESS (TABLE->nameless) #define NAMED (TABLE->named) #define LOCKCOUNT (TABLE->lockCount) _table = newNCTable(); 复制代码
obsNew函数,并非每次都新建一个Observation对象,而是从NCTable的freeList中取出空闲的对象来使用。
static Observation * obsNew(NCTable *t, SEL s, id o) { Observation *obs; /* Generally, observations are cached and we create a 'new' observation * by retrieving from the cache or by allocating a block of observations * in one go. This works nicely to both hide observations from the * garbage collector (when using gcc for GC) and to provide high * performance for situations where apps add/remove lots of observers * very frequently (poor design, but something which happens in the * real world unfortunately). */ if (t->freeList == 0) { Observation *block; if (t->chunkIndex == CHUNKSIZE) { unsigned size; t->numChunks++; size = t->numChunks * sizeof(Observation*); t->chunks = (Observation**)NSReallocateCollectable( t->chunks, size, NSScannedOption); size = CHUNKSIZE * sizeof(Observation); t->chunks[t->numChunks - 1] = (Observation*)NSAllocateCollectable(size, 0); t->chunkIndex = 0; } block = t->chunks[t->numChunks - 1]; t->freeList = &block[t->chunkIndex]; t->chunkIndex++; t->freeList->link = 0; } obs = t->freeList; t->freeList = (Observation*)obs->link; obs->link = (void*)t; obs->retained = 0; obs->next = 0; obs->selector = s; obs->observer = o; return obs; } 复制代码
/** * Deregisters observer for notifications matching name and/or object. If * either or both is nil, they act like wildcards. The observer may still * remain registered for other notifications; use -removeObserver: to remove * it from all. If observer is nil, the effect is to remove all registrees * for the specified notifications, unless both observer and name are nil, in * which case nothing is done. */ - (void) removeObserver: (id)observer name: (NSString*)name object: (id)object { if (name == nil && object == nil && observer == nil) return; /* * NB. The removal algorithm depends on an implementation characteristic * of our map tables - while enumerating a table, it is safe to remove * the entry returned by the enumerator. */ lockNCTable(TABLE); if (name == nil && object == nil) { WILDCARD = listPurge(WILDCARD, observer); } if (name == nil) { GSIMapEnumerator_t e0; GSIMapNode n0; /* * First try removing all named items set for this object. */ e0 = GSIMapEnumeratorForMap(NAMED); n0 = GSIMapEnumeratorNextNode(&e0); while (n0 != 0) { GSIMapTable m = (GSIMapTable)n0->value.ptr; NSString *thisName = (NSString*)n0->key.obj; n0 = GSIMapEnumeratorNextNode(&e0); if (object == nil) { GSIMapEnumerator_t e1 = GSIMapEnumeratorForMap(m); GSIMapNode n1 = GSIMapEnumeratorNextNode(&e1); /* * Nil object and nil name, so we step through all the maps * keyed under the current name and remove all the objects * that match the observer. */ while (n1 != 0) { GSIMapNode next = GSIMapEnumeratorNextNode(&e1); purgeMapNode(m, n1, observer); n1 = next; } } else { GSIMapNode n1; /* * Nil name, but non-nil object - we locate the map for the * specified object, and remove all the items that match * the observer. */ n1 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object); if (n1 != 0) { purgeMapNode(m, n1, observer); } } /* * If we removed all the observations keyed under this name, we * must remove the map table too. */ if (m->nodeCount == 0) { mapFree(TABLE, m); GSIMapRemoveKey(NAMED, (GSIMapKey)(id)thisName); } } /* * Now remove unnamed items */ if (object == nil) { e0 = GSIMapEnumeratorForMap(NAMELESS); n0 = GSIMapEnumeratorNextNode(&e0); while (n0 != 0) { GSIMapNode next = GSIMapEnumeratorNextNode(&e0); purgeMapNode(NAMELESS, n0, observer); n0 = next; } } else { n0 = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object); if (n0 != 0) { purgeMapNode(NAMELESS, n0, observer); } } } else { GSIMapTable m; GSIMapEnumerator_t e0; GSIMapNode n0; /* * Locate the map table for this name. */ n0 = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name)); if (n0 == 0) { unlockNCTable(TABLE); return; /* Nothing to do. */ } m = (GSIMapTable)n0->value.ptr; if (object == nil) { e0 = GSIMapEnumeratorForMap(m); n0 = GSIMapEnumeratorNextNode(&e0); while (n0 != 0) { GSIMapNode next = GSIMapEnumeratorNextNode(&e0); purgeMapNode(m, n0, observer); n0 = next; } } else { n0 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object); if (n0 != 0) { purgeMapNode(m, n0, observer); } } if (m->nodeCount == 0) { mapFree(TABLE, m); GSIMapRemoveKey(NAMED, (GSIMapKey)((id)name)); } } unlockNCTable(TABLE); } 复制代码
postNotification最终会调用_postAndRelease函数:
/** * Private method to perform the actual posting of a notification. * Release the notification before returning, or before we raise * any exception ... to avoid leaks. */ - (void) _postAndRelease: (NSNotification*)notification { Observation *o; unsigned count; NSString *name = [notification name]; id object; GSIMapNode n; GSIMapTable m; GSIArrayItem i[64]; GSIArray_t b; GSIArray a = &b; if (name == nil) { RELEASE(notification); [NSException raise: NSInvalidArgumentException format: @"Tried to post a notification with no name."]; } object = [notification object]; /* * Lock the table of observations while we traverse it. * * The table of observations contains weak pointers which are zeroed when * the observers get garbage collected. So to avoid consistency problems * we disable gc while we copy all the observations we are interested in. * We use scanned memory in the array in the case where there are more * than the 64 observers we allowed room for on the stack. */ GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i); lockNCTable(TABLE); /* * Find all the observers that specified neither NAME nor OBJECT. */ for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next) { GSIArrayAddItem(a, (GSIArrayItem)o); } /* * Find the observers that specified OBJECT, but didn't specify NAME. */ if (object) { n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object); if (n != 0) { o = purgeCollectedFromMapNode(NAMELESS, n); while (o != ENDOBS) { GSIArrayAddItem(a, (GSIArrayItem)o); o = o->next; } } } /* * Find the observers of NAME, except those observers with a non-nil OBJECT * that doesn't match the notification's OBJECT). */ if (name) { n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name)); if (n) { m = (GSIMapTable)n->value.ptr; } else { m = 0; } if (m != 0) { /* * First, observers with a matching object. */ n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object); if (n != 0) { o = purgeCollectedFromMapNode(m, n); while (o != ENDOBS) { GSIArrayAddItem(a, (GSIArrayItem)o); o = o->next; } } if (object != nil) { /* * Now observers with a nil object. */ n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil); if (n != 0) { o = purgeCollectedFromMapNode(m, n); while (o != ENDOBS) { GSIArrayAddItem(a, (GSIArrayItem)o); o = o->next; } } } } } /* Finished with the table ... we can unlock it, */ unlockNCTable(TABLE); /* * Now send all the notifications. */ count = GSIArrayCount(a); while (count-- > 0) { o = GSIArrayItemAtIndex(a, count).ext; if (o->next != 0) { NS_DURING { [o->observer performSelector: o->selector withObject: notification]; } NS_HANDLER { NSLog(@"Problem posting notification: %@", localException); } NS_ENDHANDLER } } lockNCTable(TABLE); GSIArrayEmpty(a); unlockNCTable(TABLE); RELEASE(notification); } 复制代码
步骤:查找name和object符合条件的observer,执行其任务即可。执行任务的代码即为:***[o->observer performSelector: o->selector withObject: notification];***。
而单纯的 performSelector:withObject: 并不会跟runloop什么的扯上关系,实际上就是objc_msgSend调用,iOS的performSelector是如何实现的? 。
所以,OC版本的通知,一样是同步发送所有通知到observer的。
如果是对于GSNotificationObserver,则 ***[o->observer performSelector: o->selector withObject: notification];***,实际上是调用GSNotificationObserver对象的@selector(didReceiveNotification),继而执行对应的block。
NSNotificationQueue使得通知的发送可以在一个queue中进行。实际是将通知存入queue,然后由queue等待合适的时机进行发送,而发送实际上还是经过NSNotificationCenter。
enqueueNotification是将通知放入队列,而NSNotificationQueue的使用会依赖runloop,默认是DefaultMode。
通过两个参数决定了queue发送该通知的策略,postingStyle为发送时机,coalesceMask用于合并通知。
enum { NSPostWhenIdle = 1, // post when runloop is idle NSPostASAP = 2, // post soon NSPostNow = 3 // post synchronously }; // Posting styles into notification queue typedef NSUInteger NSPostingStyle; enum { NSNotificationNoCoalescing = 0, // don't combine NSNotificationCoalescingOnName = 1, // combine all registered with same name NSNotificationCoalescingOnSender = 2 // combine all registered with same object }; // Enumeration of possible ways to combine notifications when dealing with [NSNotificationQueue]: typedef NSUInteger NSNotificationCoalescing; 复制代码
enqueueNotification如下:
/** * Sets notification to be posted to notification center at time dependent on * postingStyle, which may be either <code>NSPostNow</code> (synchronous * post), <code>NSPostASAP</code> (post soon), or <code>NSPostWhenIdle</code> * (post when runloop is idle). coalesceMask determines whether this * notification should be considered same as other ones already on the queue, * in which case they are removed through a call to * -dequeueNotificationsMatching:coalesceMask: . The modes argument * determines which [NSRunLoop] mode notification may be posted in (nil means * NSDefaultRunLoopMode). */ - (void) enqueueNotification: (NSNotification*)notification postingStyle: (NSPostingStyle)postingStyle coalesceMask: (NSUInteger)coalesceMask forModes: (NSArray*)modes { if (modes == nil) { modes = defaultMode; } if (coalesceMask != NSNotificationNoCoalescing) { [self dequeueNotificationsMatching: notification coalesceMask: coalesceMask]; } switch (postingStyle) { case NSPostNow: { NSString *mode; mode = [[NSRunLoop currentRunLoop] currentMode]; if (mode == nil || [modes indexOfObject: mode] != NSNotFound) { [_center postNotification: notification]; } } break; case NSPostASAP: add_to_queue(_asapQueue, notification, modes, _zone); break; case NSPostWhenIdle: add_to_queue(_idleQueue, notification, modes, _zone); break; } } 复制代码
如果选择了合并通知,则会调用dequeueNotificationsMatching:coalesceMask进行合并操作。
如果设置为NSPostNow,则立即调用postNotification方法。否则,调用add_to_queue将通知放入不同的queue。
static void add_to_queue(NSNotificationQueueList *queue, NSNotification *notification, NSArray *modes, NSZone *_zone) { NSNotificationQueueRegistration *item; item = NSZoneCalloc(_zone, 1, sizeof(NSNotificationQueueRegistration)); if (item == 0) { [NSException raise: NSMallocException format: @"Unable to add to notification queue"]; } item->notification = RETAIN(notification); item->name = [notification name]; item->object = [notification object]; item->modes = [modes copyWithZone: [modes zone]]; item->next = NULL; item->prev = queue->tail; queue->tail = item; if (item->prev) { item->prev->next = item; } if (!queue->head) { queue->head = item; } } 复制代码
_asapQueue和_idleQueue两个queue实际上是NSNotificationQueueList
typedef struct _NSNotificationQueueList { struct _NSNotificationQueueRegistration *head; struct _NSNotificationQueueRegistration *tail; } NSNotificationQueueList; 复制代码
NSNotificationQueue中保存的实际上NSNotificationQueueRegistration对象构成的链表。
runloop会通过调用下边三个函数,分别将不同的queue中的通知发送出来。而这三个函数在runloop中的 acceptInputForMode:beforeDate: 函数中会触发,即:
GSPrivateNotifyASAP(_currentMode); 或 GSPrivateNotifyIdle(_currentMode); [self acceptInputForMode: mode beforeDate: d]; - (BOOL) runMode: (NSString*)mode beforeDate: (NSDate*)date 复制代码
/* Function used by the NSRunLoop and friends for processing * queued notifications which should be processed at the first safe moment. */ void GSPrivateNotifyASAP(NSString *mode) GS_ATTRIB_PRIVATE; /* Function used by the NSRunLoop and friends for processing * queued notifications which should be processed when the loop is idle. */ void GSPrivateNotifyIdle(NSString *mode) GS_ATTRIB_PRIVATE; /* Function used by the NSRunLoop and friends for determining whether * there are more queued notifications to be processed. */ BOOL GSPrivateNotifyMore(NSString *mode) GS_ATTRIB_PRIVATE; 复制代码
/* * The following code handles sending of queued notifications by * NSRunLoop. */ void GSPrivateNotifyASAP(NSString *mode) { NotificationQueueList *item; GSPrivateCheckTasks(); for (item = currentList(); item; item = item->next) { if (item->queue) { notify(item->queue->_center, item->queue->_asapQueue, mode, item->queue->_zone); } } } void GSPrivateNotifyIdle(NSString *mode) { NotificationQueueList *item; for (item = currentList(); item; item = item->next) { if (item->queue) { notify(item->queue->_center, item->queue->_idleQueue, mode, item->queue->_zone); } } } BOOL GSPrivateNotifyMore(NSString *mode) { NotificationQueueList *item; for (item = currentList(); item; item = item->next) { if (item->queue != nil) { NSNotificationQueueRegistration *r; r = item->queue->_idleQueue->head; while (r != 0) { if (mode == nil || [r->modes indexOfObject: mode] != NSNotFound) { return YES; } r = r->next; } } } return NO; } 复制代码
发送操作则是notify函数。
static void notify(NSNotificationCenter *center, NSNotificationQueueList *list, NSString *mode, NSZone *zone) { BOOL allocated = NO; void *buf[100]; void **ptr = buf; unsigned len = sizeof(buf) / sizeof(*buf); unsigned pos = 0; // 取出链表头元素 NSNotificationQueueRegistration *item = list->head; /* Gather matching items into a buffer. */ while (item != 0) { if (mode == nil || [item->modes indexOfObject: mode] != NSNotFound) { if (pos == len) { unsigned want; want = (len == 0) ? 2 : len * 2; if (NO == allocated) { void *tmp; tmp = NSZoneMalloc(NSDefaultMallocZone(), want * sizeof(void*)); memcpy(tmp, (void*)ptr, len * sizeof(void*)); ptr = tmp; allocated = YES; } else { ptr = NSZoneRealloc(NSDefaultMallocZone(), ptr, want * sizeof(void*)); } len = want; } ptr[pos++] = item; } item = item->next; // head --> tail uses next link } len = pos; // Number of items found /* Posting a notification catches exceptions, so it's OK to use * retain/release of objects here as we won't get an exception * causing a leak. */ if (len > 0) { /* First, we make a note of each notification while removing the * corresponding list item from the queue ... so that when we get * round to posting the notifications we will not get problems * with another notif() trying to use the same items. */ for (pos = 0; pos < len; pos++) { item = ptr[pos]; ptr[pos] = RETAIN(item->notification); remove_from_queue(list, item, zone); } /* Now that we no longer need to worry about r-entrancy, * we step through our notifications, posting each one in turn. */ for (pos = 0; pos < len; pos++) { NSNotification *n = (NSNotification*)ptr[pos]; [center postNotification: n]; RELEASE(n); } if (allocated) { NSZoneFree(NSDefaultMallocZone(), ptr); } } } 复制代码
发送操作实际上就是一个for循环,遍历所有NSNotification,调用NSNotificationCenter的postNotification函数即完成了通知发送。
dequeueNotification操作也会根据通知合并策略(coalesceMask)来决定出队操作。
/** * Immediately remove all notifications from queue matching notification on * name and/or object as specified by coalesce mask, which is an OR * ('<code>|</code>') of the options * <code>NSNotificationCoalescingOnName</code>, * <code>NSNotificationCoalescingOnSender</code> (object), and * <code>NSNotificationNoCoalescing</code> (match only the given instance * exactly). If both of the first options are specified, notifications must * match on both attributes (not just either one). Removed notifications are * <em>not</em> posted. */ - (void) dequeueNotificationsMatching: (NSNotification*)notification coalesceMask: (NSUInteger)coalesceMask { NSNotificationQueueRegistration *item; NSNotificationQueueRegistration *prev; id name = [notification name]; id object = [notification object]; if ((coalesceMask & NSNotificationCoalescingOnName) && (coalesceMask & NSNotificationCoalescingOnSender)) { /* * find in ASAP notification in queue matching both */ for (item = _asapQueue->tail; item; item = prev) { prev = item->prev; //PENDING: should object comparison be '==' instead of isEqual?! if ((object == item->object) && [name isEqual: item->name]) { remove_from_queue(_asapQueue, item, _zone); } } /* * find in idle notification in queue matching both */ for (item = _idleQueue->tail; item; item = prev) { prev = item->prev; if ((object == item->object) && [name isEqual: item->name]) { remove_from_queue(_idleQueue, item, _zone); } } } else if ((coalesceMask & NSNotificationCoalescingOnName)) { /* * find in ASAP notification in queue matching name */ for (item = _asapQueue->tail; item; item = prev) { prev = item->prev; if ([name isEqual: item->name]) { remove_from_queue(_asapQueue, item, _zone); } } /* * find in idle notification in queue matching name */ for (item = _idleQueue->tail; item; item = prev) { prev = item->prev; if ([name isEqual: item->name]) { remove_from_queue(_idleQueue, item, _zone); } } } else if ((coalesceMask & NSNotificationCoalescingOnSender)) { /* * find in ASAP notification in queue matching sender */ for (item = _asapQueue->tail; item; item = prev) { prev = item->prev; if (object == item->object) { remove_from_queue(_asapQueue, item, _zone); } } /* * find in idle notification in queue matching sender */ for (item = _idleQueue->tail; item; item = prev) { prev = item->prev; if (object == item->object) { remove_from_queue(_idleQueue, item, _zone); } } } } 复制代码
基于name和object两个维度,涉及的数据结构,是通知中心的关键所在。添加、移除、发送均是基于该数据结构进行了相应的操作。
将notification发送到所有的observer,且observer的对应任务都执行完毕,才算通知发送成功。这个逻辑其实从上边的源码已经看出来了。
Another good thing to know is that postNotificationName: posts notifications synchronously.
看下边这段代码:
static NSString *const kNotificationName = @"1234567"; - (void)postNotificationSync { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onNotification) name:kNotificationName object:nil]; [[NSNotificationCenter defaultCenter] addObserverForName:kNotificationName object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { NSLog(@"block 1"); sleep(2); }]; [[NSNotificationCenter defaultCenter] addObserverForName:kNotificationName object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { NSLog(@"block 2"); sleep(2); }]; NSLog(@"postNotificationName %@", [NSThread currentThread]); [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationName object:nil]; NSLog(@"postNotificationName done"); } - (void)onNotification { // 默认,postNotificationName和onNotification在同一个线程中执行的。 NSLog(@"onNotification %@", [NSThread currentThread]); sleep(2); } 复制代码
输出结果为:
2020-04-14 21:51:28.425951+0800 DemoRunloop[11894:4768945] postNotificationName <NSThread: 0x600003a705c0>{number = 1, name = main} 2020-04-14 21:51:28.426169+0800 DemoRunloop[11894:4768945] onNotification <NSThread: 0x600003a705c0>{number = 1, name = main} 2020-04-14 21:51:30.427355+0800 DemoRunloop[11894:4768945] block 1 2020-04-14 21:51:32.428547+0800 DemoRunloop[11894:4768945] block 2 2020-04-14 21:51:34.429006+0800 DemoRunloop[11894:4768945] postNotificationName done 复制代码
postNotificationName done这一句是在所有observer对应的任务执行完毕,才打印出来的。且三个任务是按照addObserver时候的顺序依次执行的,且sleep都有效。
- (void)testBackgroundNotification { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onNotification) name:kNotificationName object:nil]; self.myQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL); dispatch_async(self.myQueue, ^{ NSLog(@"postNotificationName %@", [NSThread currentThread]); [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationName object:nil]; }); } - (void)onNotification { // 默认,postNotificationName和onNotification在同一个线程中执行的。 NSLog(@"onNotification %@", [NSThread currentThread]); sleep(2); } 复制代码
输出结果为:
2020-04-14 21:57:27.723143+0800 DemoRunloop[12073:4773960] postNotificationName <NSThread: 0x60000239b200>{number = 5, name = (null)} 2020-04-14 21:57:27.723352+0800 DemoRunloop[12073:4773960] onNotification <NSThread: 0x60000239b200>{number = 5, name = (null)} 复制代码
如果想要指定队列执行通知的任务,可以使用 addObserverForName:object:queue:usingBlock: 的接口。
如果跨线程使用通知,要注意避免出现死锁的场景。比如,使用下边的代码,可以产生一次死锁。
@implementation MyObject + (instancetype)sharedInstance { static MyObject *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[MyObject alloc] init]; NSLog(@"postNotificationName %@", [NSThread currentThread]); [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationName object:nil]; NSLog(@"postNotificationName done"); }); return sharedInstance; } @end - (void)testLockDispatchOnce { [[NSNotificationCenter defaultCenter] addObserverForName:kNotificationName object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { NSLog(@"block"); }]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [MyObject sharedInstance]; }); dispatch_async(dispatch_get_main_queue(), ^{ [MyObject sharedInstance]; }); } 复制代码
死锁的原因在于: