本文從源碼角度對iOS中的通知進行了解析,並對通知中心的一些特性進行了相應的解讀。node
NSNotification理所固然要包含通知name、object,且使用userInfo用於傳遞參數。git
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)***。github
基於Swift的特色,咱們能夠對Swift項目中的相關Notification使用進行一些優雅的改進。swift
強烈建議採用Alamofire中這樣的寫法:api
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。app
_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];
});
}
複製代碼
死鎖的緣由在於: