本文从源码角度对iOS中的通知进行了解析,并对通知中心的一些特性进行了相应的解读。
Swift版本
数据结构
NSNotification
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使用进行一些优雅的改进。
更加Swift化的通知
强烈建议采用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
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
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
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
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而已:
- name和object均为nil
- name为nil,object符合
- name符合,object为nil
- name和object均符合
我个人觉得这种写法给我带来了不少困惑,直接一个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中通知中心非常重要的一点!!!
遗留的问题
ObjectIdentifier
/// 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。
__SwiftValue
// 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版本
鉴于Objective-C版本与Swift版本完全不一样,这里也一并进行解析。这里采用GNUStep的源码。
数据结构
NSNotification
结构基本类似:
@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
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
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的。
- wildcard存储name和object都为nil的observer
- nameless存储name为nil,但object不为nil的observer
- named存储name不为nil的observer
freeList、chunks、cache用于查找observer时的缓存机制,提高查找效率。
addObserver
GSNotificationObserver
@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__)
addObserver:selector:name:object:
- (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;
}
removeObserver
/**
* 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
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
NSNotificationQueue使得通知的发送可以在一个queue中进行。实际是将通知存入queue,然后由queue等待合适的时机进行发送,而发送实际上还是经过NSNotificationCenter。
enqueueNotification
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如何发送通知的?
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
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两个维度,涉及的数据结构,是通知中心的关键所在。添加、移除、发送均是基于该数据结构进行了相应的操作。
postNotification是同步的
将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都有效。
默认postNotification和observer的任务是在同一线程
- (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];
});
}
死锁的原因在于:
- 子线程先执行了dispatch_once的逻辑,然后发送了通知。等待observer执行完任务,该通知才算发送完成,dispatch_once的代码段才能结束。
- 主线程执行了sharedInstance,遇到dispatch_once,要等待上一次调用dispatch_once返回,才能继续。而上一次子线程调用的dispatch_once,却因为observer的任务要在mainQueue执行,所以会等待mainQueue里边的sharedInstance调用结束。
- 这样就形成了死锁。所以,在dispatch_once中要避免跨线程操作。