關(guān)鍵類(lèi)
NSNotification
用于描述通知的類(lèi)畦粮,一個(gè)NSNotification對(duì)象就包含了一條通知的信息露泊。
// 通知名稱(chēng)
@property (readonly, copy) NSNotificationName name;
// 通知攜帶的對(duì)象
@property (nullable, readonly, retain) id object;
// 發(fā)送通知時(shí) 配置的信息
@property (nullable, readonly, copy) NSDictionary *userInfo;
// 初始化方法
- (instancetype)initWithName:(NSNotificationName)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo喉镰;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
NSNotificationCenter
是個(gè)單例類(lèi),負(fù)責(zé)管理通知的創(chuàng)建和發(fā)送滤淳,屬于最核心的類(lèi)了梧喷。它主要做三件事:
1、添加通知
2脖咐、發(fā)送通知
3铺敌、移除通知
// 全局唯一NSNotificationCenter對(duì)象
@property (class, readonly, strong) NSNotificationCenter *defaultCenter;
// 在通知中心添加一個(gè)通知
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
// 發(fā)出一個(gè)通知
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
// 移除一個(gè)通知
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;
// 添加一個(gè)通知觀察者,并在收到指定通知時(shí)執(zhí)行指定的代碼塊
// 指定代碼塊的操作隊(duì)列屁擅。如果為 nil偿凭,則在發(fā)送通知的線程上執(zhí)行代碼塊。
// 返回的觀察者對(duì)象 用于移除時(shí)使用
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (NS_SWIFT_SENDABLE ^)(NSNotification *note))block ;
注意:
1派歌、添加通知時(shí)observer和sel都不可為空弯囊,通知名稱(chēng)name和攜帶對(duì)象object可以為空痰哨。
2、注冊(cè)通知時(shí)應(yīng)避免重復(fù)注冊(cè)匾嘱,會(huì)導(dǎo)致重復(fù)處理通知信息
3斤斧、使用block時(shí),block內(nèi)可能會(huì)持有外部對(duì)象霎烙,避免循環(huán)引用撬讽。
4、消息發(fā)送類(lèi)型需要和注冊(cè)時(shí)一致悬垃。
若注冊(cè)時(shí)同時(shí)指定了名稱(chēng)和攜帶對(duì)象游昼,發(fā)到那個(gè)消息時(shí)也要指定,否則無(wú)法收到消息(更多原因會(huì)在底層分析時(shí)介紹)
5尝蠕、移除通知烘豌,ios9及macos10.11之后不需要手動(dòng)調(diào)用,dealloc已經(jīng)自動(dòng)處理看彼。
NSNotificationQueue
通知隊(duì)列廊佩,用于異步發(fā)送消息。
這個(gè)異步并不是開(kāi)啟線程闲昭,而是把通知存到雙向鏈表實(shí)現(xiàn)的隊(duì)列里面罐寨,
等待某個(gè)時(shí)機(jī)觸發(fā)時(shí)調(diào)用NSNotificationCenter的發(fā)送接口進(jìn)行發(fā)送通知。
NSNotificationQueue最終還是調(diào)用NSNotificationCenter進(jìn)行消息的分發(fā)序矩。
NSNotificationQueue主要做了兩件事:
1、添加通知到隊(duì)列
2跋破、刪除通知
// 獲取當(dāng)前當(dāng)前線程綁定的通知消息隊(duì)列
@property (class, readonly, strong) NSNotificationQueue *defaultQueue;
// 指定通知中心創(chuàng)建通知隊(duì)列
- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter ;
// 把通知添加到隊(duì)列中
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes;
// 刪除通知簸淀,把滿足合并條件的通知從隊(duì)列中刪除
- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;
通知的發(fā)送時(shí)機(jī)
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, // runloop空閑時(shí)發(fā)送通知
NSPostASAP = 2, // 當(dāng)前通知調(diào)用或者計(jì)時(shí)器結(jié)束發(fā)出通知
NSPostNow = 3 // 立刻發(fā)送或在合并通知完成之后發(fā)送
};
通知的合并策略
// 有些時(shí)候同名通知只想存在一個(gè),這時(shí)候就可以用到它了
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
NSNotificationNoCoalescing = 0,// 默認(rèn)不合并
NSNotificationCoalescingOnName = 1,// 按照通知name合并毒返,相同就合并
NSNotificationCoalescingOnSender = 2// 按照傳入的object合并租幕,相同就合并
};
底層分析
由于蘋(píng)果沒(méi)有對(duì)相關(guān)源碼開(kāi)放,所以以GNUStep源碼為基礎(chǔ)進(jìn)行研究拧簸,雖然GNUStep不是蘋(píng)果官方的源碼劲绪,但很具有參考意義,根據(jù)實(shí)現(xiàn)原理來(lái)猜測(cè)和實(shí)踐盆赤,更重要的還可以學(xué)習(xí)觀察者模式的架構(gòu)設(shè)計(jì)贾富。
1、_GSIMapTable
* A rough picture is include below:
*
*
* This is the map C - array of the buckets
* +---------------+ +---------------+
* | _GSIMapTable | /----->| nodeCount |
* |---------------| / | firstNode ----+--\
* | buckets ---+----/ | .......... | |
* | bucketCount =| size of --> | nodeCount | |
* | nodeChunks ---+--\ | firstNode | |
* | chunkCount =-+\ | | . | |
* | .... || | | . | |
* +---------------+| | | nodeCount | |
* | | | fistNode | |
* / | +---------------+ |
* ---------- v v
* / +----------+ +---------------------------+
* | | * ------+----->| Node1 | Node2 | Node3 ... | a chunk
* chunkCount | * ------+--\ +---------------------------+
* is size of = | . | \ +-------------------------------+
* | . | ->| Node n | Node n + 1 | ... | another
* +----------+ +-------------------------------+
* array pointing
* to the chunks
*
#if !defined(GSI_MAP_TABLE_T)
typedef struct _GSIMapBucket GSIMapBucket_t;
typedef struct _GSIMapNode GSIMapNode_t;
typedef GSIMapBucket_t *GSIMapBucket;
typedef GSIMapNode_t *GSIMapNode;
#endif
struct _GSIMapNode {
GSIMapNode nextInBucket; /* Linked list of bucket. */
GSIMapKey key;
#if GSI_MAP_HAS_VALUE
GSIMapVal value;
#endif
};
struct _GSIMapBucket {
uintptr_t nodeCount; /* Number of nodes in bucket. */
GSIMapNode firstNode; /* The linked list of nodes. */
};
#if defined(GSI_MAP_TABLE_T)
typedef GSI_MAP_TABLE_T *GSIMapTable;
#else
typedef struct _GSIMapTable GSIMapTable_t;
typedef GSIMapTable_t *GSIMapTable;
struct _GSIMapTable {
NSZone *zone;
uintptr_t nodeCount; /* Number of used nodes in map. */
uintptr_t bucketCount; /* Number of buckets in map. */
GSIMapBucket buckets; /* Array of buckets. */
GSIMapNode freeNodes; /* List of unused nodes. */
uintptr_t chunkCount; /* Number of chunks in array. */
GSIMapNode *nodeChunks; /* Chunks of allocated memory. */
uintptr_t increment;
#ifdef GSI_MAP_EXTRA
GSI_MAP_EXTRA extra;
#endif
};
_GSIMapTable結(jié)構(gòu)體內(nèi)的變量理解:
1牺六、buckets:存儲(chǔ)著GSIMapBucket對(duì)象的數(shù)組颤枪,可以堪稱(chēng)是一個(gè)單向鏈表,每個(gè)GSIMapBucket中的firstNode->nextInBucket表示下一個(gè)bucket淑际,依次連接畏纲。
2扇住、bucketCount表示buckets的數(shù)量。
3盗胀、nodeChunks艘蹋,表示一個(gè)數(shù)組指針,數(shù)組存儲(chǔ)所有單鏈表的首個(gè)元素node票灰。
4女阀、chunkCount 表示數(shù)組大小吹零。
5绩社、freeNodes是需要釋放的元素,是一個(gè)單向鏈表塔嬉。
_GSIMapTable其實(shí)就是一個(gè)hash表結(jié)構(gòu)屈糊,既可以以數(shù)組的形式取到每個(gè)單向鏈表首元素的榛,也可以以鏈表形式取得。通過(guò)數(shù)組能夠方便取到每個(gè)單向鏈表逻锐,再利用鏈表結(jié)構(gòu)方便增刪夫晌。
存儲(chǔ)容器NCTbl
// 根容器,NSNotificationCenter持有
typedef struct NCTbl {
Observation *wildcard; /* 鏈表結(jié)構(gòu)昧诱,保存既沒(méi)有name也沒(méi)有object的通知 */
GSIMapTable nameless; /* 存儲(chǔ)沒(méi)有name但是有object的通知 */
GSIMapTable named; /* 存儲(chǔ)帶有name的通知晓淀,不管有沒(méi)有object */
...
} NCTable;
// Observation 存儲(chǔ)觀察者和響應(yīng)結(jié)構(gòu)體,基本的存儲(chǔ)單元
typedef struct Obs {
id observer; /* 觀察者盏档,接收通知的對(duì)象 */
SEL selector; /* 響應(yīng)方法 */
struct Obs *next; /* Next item in linked list. */
...
} Observation;
wildcard:Observation結(jié)構(gòu)凶掰,存儲(chǔ)既沒(méi)有name也沒(méi)有object的通知
nameless:GSIMapTable結(jié)構(gòu),存儲(chǔ)存儲(chǔ)沒(méi)有name但是有object的通知
named:GSIMapTable蜈亩,存儲(chǔ)帶有name的通知懦窘,不管有沒(méi)有object的通知。
注冊(cè)通知
selector: (SEL)selector注冊(cè)通知
/*
observer:觀察者稚配,即通知的接收者
selector:接收到通知時(shí)的響應(yīng)方法
name: 通知name
object:攜帶對(duì)象
*/
- (void) addObserver: (id)observer
selector: (SEL)selector
name: (NSString*)name
object: (id)object {
// 前置條件判斷
......
// 創(chuàng)建一個(gè)observation對(duì)象畅涂,持有觀察者和SEL,下面進(jìn)行的所有邏輯就是為了存儲(chǔ)它
o = obsNew(TABLE, selector, observer);
/*======= case1: 如果name存在 =======*/
if (name) {
//-------- NAMED是個(gè)宏道川,表示名為named字典午衰。以name為key,從named表中獲取對(duì)應(yīng)的mapTable
n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
if (n == 0) { // 不存在冒萄,則創(chuàng)建
m = mapNew(TABLE); // 先取緩存臊岸,如果緩存沒(méi)有則新建一個(gè)map
GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
...
}
else { // 存在則把值取出來(lái) 賦值給m
m = (GSIMapTable)n->value.ptr;
}
//-------- 以object為key,從字典m中取出對(duì)應(yīng)的value宦言,其實(shí)value被MapNode的結(jié)構(gòu)包裝了一層扇单,這里不追究細(xì)節(jié)
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n == 0) {// 不存在,則創(chuàng)建
o->next = ENDOBS;
GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
}
else {
list = (Observation*)n->value.ptr;
o->next = list->next;
list->next = o;
}
}
/*======= case2:如果name為空奠旺,但object不為空 =======*/
else if (object) {
// 以object為key蜘澜,從nameless字典中取出對(duì)應(yīng)的value施流,value是個(gè)鏈表結(jié)構(gòu)
n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
// 不存在則新建鏈表,并存到map中
if (n == 0) {
o->next = ENDOBS;
GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
}
else { // 存在 則把值接到鏈表的節(jié)點(diǎn)上
...
}
}
/*======= case3:name 和 object 都為空 則存儲(chǔ)到wildcard鏈表中 =======*/
else {
o->next = WILDCARD;
WILDCARD = o;
}
}
從上面介紹的存儲(chǔ)容器中我們了解到NCTable結(jié)構(gòu)體中核心的三個(gè)變量以及功能:wildcard鄙信、named瞪醋、nameless,在源碼中直接用宏定義表示了:WILDCARD装诡、NAMELESS银受、NAMED。
case1: 存在name(無(wú)論object是否存在)
1鸦采、注冊(cè)通知宾巍,如果通知的name存在,則以name為key從named字典中取出值n(這個(gè)n其實(shí)被MapNode包裝了一層渔伯,便于理解這里直接認(rèn)為沒(méi)有包裝)顶霞,這個(gè)n還是個(gè)字典,各種判空新建邏輯不討論锣吼。
2选浑、然后以object為key,從字典n中取出對(duì)應(yīng)的值玄叠,這個(gè)值就是Observation類(lèi)型(后面簡(jiǎn)稱(chēng)obs)的鏈表古徒,然后把剛開(kāi)始創(chuàng)建的obs對(duì)象o存儲(chǔ)進(jìn)去。
數(shù)據(jù)結(jié)構(gòu)關(guān)系圖
如果注冊(cè)通知時(shí)傳入name读恃,那么會(huì)是一個(gè)雙層的存儲(chǔ)結(jié)構(gòu)
1隧膘、找到NCTable中的named表,這個(gè)表存儲(chǔ)了還有name的通知
2寺惫、以name作為key舀寓,找到value,這個(gè)value依然是一個(gè)map
3肌蜻、map的結(jié)構(gòu)是以object作為key,obs對(duì)象為value必尼,這個(gè)obs對(duì)象的結(jié)構(gòu)上面已經(jīng)解釋?zhuān)饕鎯?chǔ)了observer & SEL
case2: 只存在object
1蒋搜、以object為key,從nameless字典中取出value判莉,此value是個(gè)obs類(lèi)型的鏈表豆挽。
2、把創(chuàng)建的obs類(lèi)型的對(duì)象o存儲(chǔ)到鏈表中券盅。
數(shù)據(jù)結(jié)構(gòu)關(guān)系圖
只存在object時(shí)存儲(chǔ)只有一層帮哈,那就是object和obs對(duì)象之間的映射
case3: 沒(méi)有name和object
這種情況直接把obs對(duì)象存放在了Observation *wildcard 鏈表結(jié)構(gòu)中。
block - 注冊(cè)通知
- (id) addObserverForName: (NSString *)name
object: (id)object
queue: (NSOperationQueue *)queue
usingBlock: (GSNotificationBlock)block
{
// 創(chuàng)建一個(gè)臨時(shí)觀察者
GSNotificationObserver *observer =
[[GSNotificationObserver alloc] initWithQueue: queue block: block];
// 調(diào)用了@selector的注冊(cè)方法
[self addObserver: observer
selector: @selector(didReceiveNotification:)
name: name
object: object];
return observer;
}
- (void) didReceiveNotification: (NSNotification *)notif {
if (_queue != nil) {
GSNotificationBlockOperation *op = [[GSNotificationBlockOperation alloc]
initWithNotification: notif block: _block];
[_queue addOperation: op];
} else {
CALL_BLOCK(_block, notif);
}
}
這個(gè)接口依賴(lài)于selector注冊(cè)锰镀,娘侍,只是多了一層代理觀察者GSNotificationObserver咖刃,
1、創(chuàng)建一個(gè)GSNotificationObserver類(lèi)型的對(duì)象observer憾筏,并把queue和block保存下來(lái)
2嚎杨、調(diào)用接口1進(jìn)行通知的注冊(cè)
3、接收到通知時(shí)會(huì)響應(yīng)observer的didReceiveNotification:方法氧腰,然后在didReceiveNotification:中把block拋給指定的queue去執(zhí)行枫浙。
從上述介紹可以總結(jié):
1、存儲(chǔ)是以name和object為維度的古拴,即判定是不是同一個(gè)通知要從name和object區(qū)分箩帚,如果他們都相同則認(rèn)為是同一個(gè)通知,后面包括查找邏輯黄痪、刪除邏輯都是以這兩個(gè)為維度的紧帕,問(wèn)題列表中的第九題也迎刃而解了。
2满力、理解數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)是整個(gè)通知機(jī)制的核心焕参,其他功能只是在此基礎(chǔ)上擴(kuò)展了一些邏輯。
3油额、存儲(chǔ)過(guò)程并沒(méi)有做去重操作叠纷,這也解釋了為什么同一個(gè)通知注冊(cè)多次則響應(yīng)多次。
發(fā)送通知
// 發(fā)送通知
- (void) postNotificationName: (NSString*)name
object: (id)object
userInfo: (NSDictionary*)info
{
// 構(gòu)造一個(gè)GSNotification對(duì)象潦嘶, GSNotification繼承了NSNotification
GSNotification *notification;
notification = (id)NSAllocateObject(concrete, 0, NSDefaultMallocZone());
notification->_name = [name copyWithZone: [self zone]];
notification->_object = [object retain];
notification->_info = [info retain];
// 進(jìn)行發(fā)送操作
[self _postAndRelease: notification];
}
//發(fā)送通知的核心函數(shù)涩嚣,主要做了三件事:查找通知、發(fā)送掂僵、釋放資源
- (void) _postAndRelease: (NSNotification*)notification {
//step1: 從named航厚、nameless、wildcard表中查找對(duì)應(yīng)的通知
...
//step2:執(zhí)行發(fā)送锰蓬,即調(diào)用performSelector執(zhí)行響應(yīng)方法幔睬,從這里可以看出是同步的
[o->observer performSelector: o->selector
withObject: notification];
//step3: 釋放資源
RELEASE(notification);
}
上述代碼注釋說(shuō)的很清晰了,主要做了三件事
1芹扭、通過(guò)name & object 查找到所有的obs對(duì)象(保存了observer和sel)麻顶,放到數(shù)組中。
2舱卡、通過(guò)performSelector:逐一調(diào)用sel辅肾,這是個(gè)同步操作。
3轮锥、釋放notification對(duì)象矫钓。發(fā)送過(guò)程的概述:
1、從三個(gè)存儲(chǔ)容器中:named、nameless新娜、wildcard去查找對(duì)應(yīng)的obs對(duì)象赵辕。
2、然后通過(guò)performSelector:逐一調(diào)用響應(yīng)方法杯活,這就完成了發(fā)送流程匆帚。核心點(diǎn):
1、同步發(fā)送
2旁钧、遍歷所有列表吸重,即注冊(cè)多次通知就會(huì)響應(yīng)多次
刪除通知
1、查找時(shí)仍然以name和object為維度的歪今,再加上observer做區(qū)分嚎幸。
2、因?yàn)椴檎視r(shí)做了這個(gè)鏈表的遍歷寄猩,所以刪除時(shí)會(huì)把重復(fù)的通知全都刪除掉嫉晶。
更多源碼下載GNUStep
異步通知
入隊(duì)
入隊(duì)
下面為精簡(jiǎn)版的源碼,看源碼的注釋?zhuān)旧夏苊靼状笾逻壿?br>
1田篇、根據(jù)coalesceMask參數(shù)判斷是否合并通知替废。
2、接著根據(jù)postingStyle參數(shù)泊柬,判斷通知發(fā)送的時(shí)機(jī)椎镣,
如果不是立即發(fā)送則把通知加入到隊(duì)列中:_asapQueue、_idleQueue兽赁。
核心點(diǎn):
1状答、隊(duì)列是雙向鏈表實(shí)現(xiàn)
2、當(dāng)postingStyle值是立即發(fā)送時(shí)刀崖,調(diào)用的是NSNotificationCenter進(jìn)行發(fā)送的惊科,所以NSNotificationQueue還是依賴(lài)NSNotificationCenter進(jìn)行發(fā)送
/*
* 把要發(fā)送的通知添加到隊(duì)列,等待發(fā)送
* NSPostingStyle 和 coalesceMask在上面的類(lèi)結(jié)構(gòu)中有介紹
* modes這個(gè)就和runloop有關(guān)了亮钦,指的是runloop的mode
*/
- (void) enqueueNotification: (NSNotification*)notification
postingStyle: (NSPostingStyle)postingStyle
coalesceMask: (NSUInteger)coalesceMask
forModes: (NSArray*)modes
{
......
// 判斷是否需要合并通知
if (coalesceMask != NSNotificationNoCoalescing) {
[self dequeueNotificationsMatching: notification
coalesceMask: coalesceMask];
}
switch (postingStyle) {
case NSPostNow: {
...
// 如果是立馬發(fā)送馆截,則調(diào)用NSNotificationCenter進(jìn)行發(fā)送
[_center postNotification: notification];
break;
}
case NSPostASAP:
// 添加到_asapQueue隊(duì)列,等待發(fā)送
add_to_queue(_asapQueue, notification, modes, _zone);
break;
case NSPostWhenIdle:
// 添加到_idleQueue隊(duì)列蜂莉,等待發(fā)送
add_to_queue(_idleQueue, notification, modes, _zone);
break;
}
}
發(fā)送通知
這里截取了發(fā)送通知的核心代碼孙咪,這個(gè)發(fā)送通知邏輯如下:
1、runloop觸發(fā)某個(gè)時(shí)機(jī)巡语,調(diào)用GSPrivateNotifyASAP()和GSPrivateNotifyIdle()方法,這兩個(gè)方法最終都調(diào)用了notify()方法淮菠。
2男公、notify()所做的事情就是調(diào)用NSNotificationCenter的postNotification:進(jìn)行發(fā)送通知。
static void notify(NSNotificationCenter *center,
NSNotificationQueueList *list,
NSString *mode, NSZone *zone)
{
......
// 循環(huán)遍歷發(fā)送通知
for (pos = 0; pos < len; pos++)
{
NSNotification *n = (NSNotification*)ptr[pos];
[center postNotification: n];
RELEASE(n);
}
......
}
// 發(fā)送_asapQueue中的通知
void GSPrivateNotifyASAP(NSString *mode)
{
notify(item->queue->_center,
item->queue->_asapQueue,
mode,
item->queue->_zone);
}
// 發(fā)送_idleQueue中的通知
void GSPrivateNotifyIdle(NSString *mode)
{
notify(item->queue->_center,
item->queue->_idleQueue,
mode,
item->queue->_zone);
}
NSNotificationQueue總結(jié):
1、依賴(lài)runloop枢赔,所以如果在其他子線程使用NSNotificationQueue澄阳,需要開(kāi)啟runloop。
2踏拜、最終還是通過(guò)NSNotificationCenter進(jìn)行發(fā)送通知碎赢,所以這個(gè)角度講它還是同步的。
3速梗、所謂異步肮塞,指的是非實(shí)時(shí)發(fā)送而是在合適的時(shí)機(jī)發(fā)送,并沒(méi)有開(kāi)啟異步線程姻锁。
主線程響應(yīng)通知
異步線程發(fā)送通知?jiǎng)t響應(yīng)函數(shù)也是在異步線程枕赵,如果執(zhí)行UI刷新相關(guān)的話就會(huì)出問(wèn)題,那么如何保證在主線程響應(yīng)通知呢位隶?
其實(shí)也是比較常見(jiàn)的問(wèn)題了拷窜,基本上解決方式如下幾種:
1、使用addObserverForName: object: queue: usingBlock方法注冊(cè)通知涧黄,指定在mainqueue上響應(yīng)block篮昧。
2、在主線程注冊(cè)一個(gè)machPort笋妥,它是用來(lái)做線程通信的懊昨,當(dāng)在異步線程收到通知,然后給machPort發(fā)送消息挽鞠,這樣肯定是在主線程處理的疚颊。
問(wèn)題列表
1、通知的實(shí)現(xiàn)原理(結(jié)構(gòu)設(shè)計(jì)信认、通知如何存儲(chǔ)的材义、name&observer&SEL之間的關(guān)系等)
通知里面主要有三個(gè)類(lèi)NSNotification通知對(duì)象,包括name嫁赏,object其掂,userinfo
NSNotificationCenter,通知中心潦蝇,負(fù)責(zé)添加款熬、發(fā)送、移除通知攘乒。
NSNotificationQueue贤牛,通知隊(duì)列,負(fù)責(zé)某些時(shí)機(jī)觸發(fā)調(diào)用通知则酝,最后調(diào)用還是NSNotificationCenter通知中心 post通知殉簸。
通知隊(duì)列里的異步,實(shí)際上不是真正的異步,更多是延時(shí)發(fā)送般卑,利用了runloop的時(shí)機(jī)來(lái)觸發(fā)武鲁。
根據(jù)開(kāi)源代碼GNUStep推測(cè),通知時(shí)結(jié)構(gòu)體蝠检,通過(guò)雙向鏈表進(jìn)行數(shù)據(jù)存儲(chǔ)
// 根容器沐鼠,NSNotificationCenter持有
typedef struct NCTbl {
Observation *wildcard; /* 鏈表結(jié)構(gòu),保存既沒(méi)有name也沒(méi)有object的通知 */
GSIMapTable nameless; /* 存儲(chǔ)沒(méi)有name但是有object的通知 */
GSIMapTable named; /* 存儲(chǔ)帶有name的通知叹谁,不管有沒(méi)有object */
...
} NCTable;
// Observation 存儲(chǔ)觀察者和響應(yīng)結(jié)構(gòu)體饲梭,基本的存儲(chǔ)單元
typedef struct Obs {
id observer; /* 觀察者,接收通知的對(duì)象 */
SEL selector; /* 響應(yīng)方法 */
struct Obs *next; /* Next item in linked list. */
...
} Observation;
通知是以key value的形式存儲(chǔ),
需要重點(diǎn)強(qiáng)調(diào):
通知以 name和object兩個(gè)緯度來(lái)存儲(chǔ)相關(guān)通知內(nèi)容,也就是我們添加通知的時(shí)候傳入的兩個(gè)不同的方法.
name&observer&SEL之間的關(guān)系:name作為key, observer作為觀察者對(duì)象,當(dāng)合適時(shí)機(jī)觸發(fā)就會(huì)調(diào)用observer的SEL
2本慕、通知的發(fā)送時(shí)同步的排拷,還是異步的
同步發(fā)送,因?yàn)橐{(diào)用消息轉(zhuǎn)發(fā).
所謂異步,指的是非實(shí)時(shí)發(fā)送而是在合適的時(shí)機(jī)發(fā)送锅尘,并沒(méi)有開(kāi)啟異步線程.
3监氢、NSNotificationCenter 接受消息和發(fā)送消息是在一個(gè)線程里嗎?如何異步發(fā)送消息
是的, 異步線程發(fā)送通知?jiǎng)t響應(yīng)函數(shù)也是在異步線程.
異步發(fā)送通知可以開(kāi)啟異步線程發(fā)送即可.
4藤违、NSNotificationQueue是異步還是同步發(fā)送浪腐?在哪個(gè)線程響應(yīng)?
// 表示通知的發(fā)送時(shí)機(jī)
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, // runloop空閑時(shí)發(fā)送通知
NSPostASAP = 2, // 盡快發(fā)送,這種時(shí)機(jī)是穿插在每次事件完成期間來(lái)做的
NSPostNow = 3 // 立刻發(fā)送或者合并通知完成之后發(fā)送
};
NSPostWhenIdle:異步發(fā)送
NSPostASAP:異步發(fā)送
NSPostNow:同步發(fā)送NSNotificationCenter都是同步發(fā)送的顿乒,
而關(guān)于NSNotificationQueue的異步發(fā)送议街,從線程的角度看并不是真正的異步發(fā)送,或可稱(chēng)為延時(shí)發(fā)送璧榄,它是利用了runloop的時(shí)機(jī)來(lái)觸發(fā)的特漩。
異步線程發(fā)送通知?jiǎng)t響應(yīng)函數(shù)也是在異步線程,主線程發(fā)送則在主線程。
5骨杂、NSNotificationQueue和runloop的關(guān)系
NSNotificationQueue依賴(lài)runloop. 因?yàn)橥ㄖ?duì)列要在runloop回調(diào)的某個(gè)時(shí)機(jī)調(diào)用通知中心發(fā)送通知涂身。
6、如何保證通知接收的線程在主線程
兩種方式:
1搓蚪、系統(tǒng)接受通知的API指定隊(duì)列
2蛤售、NSMachPort的方式 通過(guò)在主線程的 runloop 中添加 machPort,設(shè)置這個(gè) port 的 delegate妒潭,通過(guò)這個(gè) Port 其他線程可以跟主線程通信悴能,在這個(gè) port 的代理回調(diào)中執(zhí)行的代碼肯定在主線程中運(yùn)行,所以雳灾,在這里調(diào)用 NSNotificationCenter 發(fā)送通知即可
7漠酿、頁(yè)面銷(xiāo)毀時(shí)不移除通知會(huì)崩潰嗎?
iOS9.0之前,會(huì)crash谎亩,原因:通知中心對(duì)觀察者的引用是 unsafe_unretained记靡,導(dǎo)致當(dāng)觀察者釋放的時(shí)候谈竿,觀察者的指針值并不為nil,出現(xiàn)野指針.
iOS9.0之后摸吠,不會(huì)crash,原因:通知中心對(duì)觀察者的引用是 weak嚎花。
8寸痢、多次添加同一個(gè)通知會(huì)是什么結(jié)果?多次移除通知呢
多次添加同一個(gè)通知紊选,會(huì)導(dǎo)致發(fā)送一次這個(gè)通知的時(shí)候啼止,響應(yīng)多次通知回調(diào)。
多次移除通知不會(huì)產(chǎn)生crash兵罢。
9献烦、下面的方式能接收到通知嗎?為什么
// 發(fā)送通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];
// 接收通知
[NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];
不能卖词。根據(jù)推測(cè)的通知中心存儲(chǔ)通知觀察者的結(jié)構(gòu)巩那。
// 根容器,NSNotificationCenter持有
typedef struct NCTbl {
Observation *wildcard; /* 鏈表結(jié)構(gòu)此蜈,保存既沒(méi)有name也沒(méi)有object的通知 */
GSIMapTable nameless; /* 存儲(chǔ)沒(méi)有name但是有object的通知 */
GSIMapTable named; /* 存儲(chǔ)帶有name的通知即横,不管有沒(méi)有object */
...
} NCTable;
當(dāng)添加通知監(jiān)聽(tīng)的時(shí)候,我們傳入了name和object裆赵,所以东囚,觀察者的存儲(chǔ)鏈表是這樣的:named表:key(name) : value->key(object) : value(Observation)
因此在發(fā)送通知的時(shí)候,如果只傳入name而并沒(méi)有傳入object战授,是找不到Observation的页藻,也就不能執(zhí)行觀察者回調(diào)。