原創(chuàng):知識(shí)點(diǎn)總結(jié)性文章
創(chuàng)作不易厌均,請(qǐng)珍惜,之后會(huì)持續(xù)更新聂示,不斷完善
個(gè)人比較喜歡做筆記和寫總結(jié)域携,畢竟好記性不如爛筆頭哈哈,這些文章記錄了我的IOS成長(zhǎng)歷程鱼喉,希望能與大家一起進(jìn)步
溫馨提示:由于簡(jiǎn)書不支持目錄跳轉(zhuǎn)秀鞭,大家可通過(guò)command + F 輸入目錄標(biāo)題后迅速尋找到你所需要的內(nèi)容
目錄
- 一、使用
- 1扛禽、提供的屬性和方法
- 2锋边、隊(duì)列的合并策略和發(fā)送時(shí)機(jī)
- 3、注意點(diǎn)
- 二编曼、注冊(cè)通知源碼解析
- 1豆巨、存儲(chǔ)容器
- 2、解析注冊(cè)通知方法
- 3掐场、判斷是否是同一個(gè)通知的3種情況
- 三往扔、發(fā)送通知與刪除通知源碼解析
- 1贩猎、發(fā)送通知源碼解析
- 2、刪除通知源碼解析
- 四萍膛、異步通知
- 1吭服、NSNotificationQueue的異步發(fā)送
- 2、把要發(fā)送的通知添加到隊(duì)列蝗罗,等待發(fā)送
- 3艇棕、發(fā)送通知
- 4、主線程響應(yīng)通知
- Demo
- 參考文獻(xiàn)
一绿饵、使用
1欠肾、提供的屬性和方法
NSNotification
- (NSString*) name; // 通知的name
- (id) object; // 攜帶的對(duì)象
- (NSDictionary*) userInfo; // 配置信息
NSNotificationCenter
// 添加通知
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
// 發(fā)送通知
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
// 刪除通知
- (void)removeObserver:(id)observer;
NSNotificationQueue
// 把通知添加到隊(duì)列中,NSPostingStyle是個(gè)枚舉
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
// 刪除通知拟赊,把滿足合并條件的通知從隊(duì)列中刪除
- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;
用于異步發(fā)送消息的通知隊(duì)列刺桃,這個(gè)異步并不是開啟線程,而是把通知存到雙向鏈表實(shí)現(xiàn)的隊(duì)列里面吸祟,等待某個(gè)時(shí)機(jī)觸發(fā)瑟慈。觸發(fā)時(shí)調(diào)用NSNotificationCenter
的發(fā)送接口進(jìn)行發(fā)送通知,這么看NSNotificationQueue
最終還是調(diào)用NSNotificationCenter
進(jìn)行消息的分發(fā)屋匕,另外NSNotificationQueue
是依賴runloop
的葛碧,所以如果線程的runloop
未開啟則無(wú)效。
2过吻、隊(duì)列的合并策略和發(fā)送時(shí)機(jī)
把通知添加到隊(duì)列等待發(fā)送进泼,同時(shí)提供了一些附加條件供開發(fā)者選擇,如:什么時(shí)候發(fā)送通知纤虽、如何合并通知等乳绕,系統(tǒng)給了如下定義。
表示通知的發(fā)送時(shí)機(jī)
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, // runloop空閑時(shí)發(fā)送通知
NSPostASAP = 2, // 盡快發(fā)送逼纸,這種情況稍微復(fù)雜洋措,這種時(shí)機(jī)是穿插在每次事件完成期間來(lái)做的
NSPostNow = 3 // 立刻發(fā)送或者合并通知完成之后發(fā)送
};
通知合并的策略,有些時(shí)候同名通知只想存在一個(gè)杰刽,這時(shí)候就可以用到它了
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
NSNotificationNoCoalescing = 0, // 默認(rèn)不合并
NSNotificationCoalescingOnName = 1, // 只要name相同菠发,就認(rèn)為是相同通知
NSNotificationCoalescingOnSender = 2 // object相同
};
3、注意點(diǎn)
頁(yè)面銷毀時(shí)不移除通知會(huì)崩潰嗎
可以贺嫂,因?yàn)?code>notificationcenter對(duì)觀察者的引用是weak
滓鸠,當(dāng)觀察者釋放的時(shí)候,觀察者的指針值被置為nil
多次添加同一個(gè)通知會(huì)是什么結(jié)果第喳?多次移除通知呢
會(huì)調(diào)用多次observer
的action
哥力。多次移除沒有任何影響。
二、注冊(cè)通知源碼解析
1吩跋、存儲(chǔ)容器
NCTable是根容器寞射,由NSNotificationCenter持有
NCTable
結(jié)構(gòu)體中核心的三個(gè)變量:wildcard
、named
锌钮、nameless
桥温,在源碼中直接用宏定義表示了:WILDCARD
、NAMELESS
梁丘、NAMED
侵浸。
typedef struct NCTbl
{
// 鏈表結(jié)構(gòu),保存既沒有name也沒有object的通知
Observation *wildcard;
// 存儲(chǔ)沒有name但是有object的通知
GSIMapTable nameless;
// 存儲(chǔ)帶有name的通知氛谜,不管有沒有object
GSIMapTable named;
...
} NCTable;
Observation是存儲(chǔ)觀察者和響應(yīng)方法的結(jié)構(gòu)體
typedef struct Obs
{
id observer;// 觀察者掏觉,接收通知的對(duì)象
SEL selector;// 響應(yīng)方法
struct Obs *next;// 鏈表中的下一個(gè)Observation
...
} Observation;
2、解析注冊(cè)通知方法
- 判定是不是同一個(gè)通知要從
name
和object
區(qū)分值漫,如果他們都相同則認(rèn)為是同一個(gè)通知澳腹,后面包括查找邏輯、刪除邏輯都以此為基礎(chǔ)杨何。 - 存儲(chǔ)過(guò)程并沒有做去重操作酱塔,這也解釋了為什么同一個(gè)通知注冊(cè)多次則響應(yīng)多次
a、提供給外界使用的注冊(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);
...
}
b埃跷、情況一:如果name存在
if (name) {...}
? NAMED
是個(gè)宏蕊玷,表示名為named
的字典。如果通知的name
存在弥雹,則以name
為key
從named
字典中取出值n
(這個(gè)n
其實(shí)被MapNode
包裝了一層集畅,便于理解這里直接認(rèn)為沒有包裝),這個(gè)n
還是個(gè)字典缅糟。
n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
? n
不存在,則先取緩存祷愉,如果緩存沒有則新建一個(gè)map
if (n == 0)
{
m = mapNew(TABLE);
GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
...
}
? n
存在則把值取出來(lái)賦值給m
else
{
m = (GSIMapTable)n->value.ptr;
}
? 然后以object
為key
窗宦,從字典中取出對(duì)應(yīng)的值,這個(gè)值就是Observation
類型的鏈表二鳄,然后把剛開始創(chuàng)建的Observation
對(duì)象o
存儲(chǔ)進(jìn)去赴涵。
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;
}
c订讼、情況二:如果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);
}
? 存在則把值接到鏈表的節(jié)點(diǎn)上
else
{
...
}
d、情況三:name 和 object 都為空則存儲(chǔ)到wildcard鏈表中
else
{
o->next = WILDCARD;
WILDCARD = o;
}
3程拭、判斷是否是同一個(gè)通知的3種情況
情況一:存在name(無(wú)論object是否存在)
如果注冊(cè)通知時(shí)傳入name
定踱,那么會(huì)是一個(gè)雙層的存儲(chǔ)結(jié)構(gòu)。首先找到NCTable
中的named
表恃鞋,這個(gè)表存儲(chǔ)了name
的通知崖媚。接著以name
作為key
,找到value
恤浪,這個(gè)value
依然是一個(gè)map
畅哑。最后,map
的結(jié)構(gòu)是以object
作為key
水由,Observation
對(duì)象為value
荠呐,這個(gè)Observation
對(duì)象的結(jié)構(gòu)上面已經(jīng)解釋,主要存儲(chǔ)了observer & SEL
绷杜。
情況二:只存在object
以object
為key
直秆,從nameless
字典中取出value
,此value
是個(gè)Observation
類型的鏈表鞭盟。接著把創(chuàng)建的Observation
類型的對(duì)象o
存儲(chǔ)到鏈表中圾结。只存在object
時(shí)存儲(chǔ)只有一層,那就是object
和Observation
對(duì)象之間的映射齿诉。
情況三:沒有name和object
這種情況直接把Observation
對(duì)象存放在了Observation *wildcard
鏈表結(jié)構(gòu)中筝野。
三、發(fā)送通知與刪除通知源碼解析
1粤剧、發(fā)送通知源碼解析
發(fā)送通知的核心邏輯比較簡(jiǎn)單歇竟,基本上就是查找和調(diào)用響應(yīng)方法,從三個(gè)存儲(chǔ)容器中:named
抵恋、nameless
焕议、wildcard
去查找對(duì)應(yīng)的Observation
對(duì)象,然后通過(guò)performSelector
:逐一調(diào)用響應(yīng)方法弧关,這就完成了發(fā)送流程盅安。
a、解析發(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];
b别瞭、發(fā)送通知的核心函數(shù)
主要做了三件事:查找通知、發(fā)送株憾、釋放資源蝙寨。
- (void)_postAndRelease: (NSNotification*)notification
{
...
}
? 通過(guò)name & object
從named
晒衩、nameless
、wildcard
表中查找對(duì)應(yīng)的通知(保存了observer
和sel
)墙歪。
...
? 執(zhí)行發(fā)送听系,即調(diào)用performSelector
執(zhí)行響應(yīng)方法,從這里可以看出是同步的箱亿。
[o->observer performSelector: o->selector
withObject: notification];
? 釋放notification
對(duì)象跛锌。
RELEASE(notification);
2、刪除通知源碼解析
因?yàn)椴檎視r(shí)做了這個(gè)鏈表的遍歷届惋,所以刪除時(shí)會(huì)把重復(fù)的通知全都刪除掉
- (void)removeObserver: (id)observer
{
if (observer == nil) return;
[self removeObserver: observer name: nil object: nil];
}
查找時(shí)仍然以name
和object
為準(zhǔn)髓帽,再加上observer
做區(qū)分。
- (void)removeObserver: (id)observer name: (NSString*)name object: (id)object
{
if (name == nil && object == nil && observer == nil)
return;
...
}
四脑豹、異步通知
1郑藏、NSNotificationQueue的異步發(fā)送
上面介紹的NSNotificationCenter
都是同步發(fā)送的,接受消息和發(fā)送消息是在一個(gè)線程里瘩欺。這里介紹關(guān)于NSNotificationQueue
的異步發(fā)送必盖,通過(guò)NSNotificationQueue
將通知添加到隊(duì)列當(dāng)中,立即將控制權(quán)返回給調(diào)用者俱饿,在合適的時(shí)機(jī)發(fā)送通知歌粥,從而不會(huì)阻塞當(dāng)前的調(diào)用。從線程的角度看并不是真正的異步發(fā)送拍埠,或可稱為延時(shí)發(fā)送失驶,它是利用了runloop
的時(shí)機(jī)來(lái)觸發(fā)的,所以如果在其他子線程使用NSNotificationQueue
枣购,需要開啟runloop
嬉探。由于最終還是通過(guò)NSNotificationCenter
進(jìn)行發(fā)送通知,所以從這個(gè)角度講它還是同步的棉圈。所謂異步涩堤,指的是非實(shí)時(shí)發(fā)送而是在合適的時(shí)機(jī)發(fā)送,并沒有開啟異步線程分瘾。
2胎围、把要發(fā)送的通知添加到隊(duì)列,等待發(fā)送
NSPostingStyle
和 coalesceMask
在上面的類結(jié)構(gòu)中有介紹德召。modes
這個(gè)就和runloop
有關(guān)了白魂,指的是runloop
的mode
。
- (void) enqueueNotification: (NSNotification*)notification
postingStyle: (NSPostingStyle)postingStyle
coalesceMask: (NSUInteger)coalesceMask
forModes: (NSArray*)modes
{
...
}
? 根據(jù)coalesceMask
參數(shù)判斷是否合并通知
if (coalesceMask != NSNotificationNoCoalescing)
{
[self dequeueNotificationsMatching: notification
coalesceMask: coalesceMask];
}
? 接著根據(jù)postingStyle
參數(shù)氏捞,判斷通知發(fā)送的時(shí)機(jī)
switch (postingStyle)
{
...
}
? runloop
立即回調(diào)通知方法,同步發(fā)送
case NSPostNow:
{
// 如果是立馬發(fā)送冒版,則調(diào)用NSNotificationCenter進(jìn)行發(fā)送
[_center postNotification: notification];
}
? runloop
在執(zhí)行timer
事件或sources
事件的時(shí)候回調(diào)通知方法液茎,異步發(fā)送
case NSPostASAP:
// 添加到_asapQueue隊(duì)列,等待發(fā)送
add_to_queue(_asapQueue, notification, modes, _zone);
? runloop
空閑的時(shí)候回調(diào)通知方法,異步發(fā)送
case NSPostWhenIdle:
// 添加到_idleQueue隊(duì)列捆等,等待發(fā)送
add_to_queue(_idleQueue, notification, modes, _zone);
3滞造、發(fā)送通知
runloop
觸發(fā)某個(gè)時(shí)機(jī),調(diào)用GSPrivateNotifyASAP()
和GSPrivateNotifyIdle()
方法栋烤,這兩個(gè)方法最終都調(diào)用了notify()
方法谒养。notify()
所做的事情就是調(diào)用NSNotificationCenter
的postNotification:
進(jìn)行發(fā)送通知。
a明郭、發(fā)送_asapQueue中的通知
void GSPrivateNotifyASAP(NSString *mode)
{
notify(item->queue->_center,
item->queue->_asapQueue,
mode,
item->queue->_zone);
}
b买窟、發(fā)送_idleQueue中的通知
void GSPrivateNotifyIdle(NSString *mode)
{
notify(item->queue->_center,
item->queue->_idleQueue,
mode,
item->queue->_zone);
}
c、循環(huán)遍歷發(fā)送通知
static void notify(NSNotificationCenter *center,
NSNotificationQueueList *list,
NSString *mode, NSZone *zone)
{
for (pos = 0; pos < len; pos++)
{
NSNotification *n = (NSNotification*)ptr[pos];
[center postNotification: n];
RELEASE(n);
}
}
4薯定、主線程響應(yīng)通知
異步線程發(fā)送通知?jiǎng)t響應(yīng)函數(shù)也是在異步線程始绍,如果執(zhí)行UI刷新相關(guān)的話就會(huì)出現(xiàn)問題,那么如何保證在主線程響應(yīng)通知呢话侄?可以使用addObserverForName: object: queue: usingBlock
方法注冊(cè)通知亏推,指定在mainqueue
上響應(yīng)block
。
Demo
Demo在我的Github上年堆,歡迎下載吞杭。
BasicsDemo