IOS基礎(chǔ)原理:Notifications

原創(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)用多次observeraction哥力。多次移除沒有任何影響。


二、注冊(cè)通知源碼解析

1吩跋、存儲(chǔ)容器

NCTable是根容器寞射,由NSNotificationCenter持有

NCTable結(jié)構(gòu)體中核心的三個(gè)變量:wildcardnamed锌钮、nameless桥温,在源碼中直接用宏定義表示了:WILDCARDNAMELESS梁丘、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è)通知要從nameobject區(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存在弥雹,則以namekeynamed字典中取出值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;
}

? 然后以objectkey窗宦,從字典中取出對(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)
{
    ...
}

? 以objectkey,從nameless字典中取出對(duì)應(yīng)的valuevalue是個(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

objectkey直秆,從nameless字典中取出value,此value是個(gè)Observation類型的鏈表鞭盟。接著把創(chuàng)建的Observation類型的對(duì)象o存儲(chǔ)到鏈表中圾结。只存在object時(shí)存儲(chǔ)只有一層,那就是objectObservation對(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 & objectnamed晒衩、namelesswildcard表中查找對(duì)應(yīng)的通知(保存了observersel)墙歪。

...

? 執(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í)仍然以nameobject為準(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ā)送

NSPostingStylecoalesceMask在上面的類結(jié)構(gòu)中有介紹德召。modes這個(gè)就和runloop有關(guān)了白魂,指的是runloopmode

- (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)用NSNotificationCenterpostNotification:進(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

參考文獻(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者变丧。
  • 序言:七十年代末芽狗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子锄贷,更是在濱河造成了極大的恐慌译蒂,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谊却,死亡現(xiàn)場(chǎng)離奇詭異柔昼,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)炎辨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門捕透,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人碴萧,你說(shuō)我怎么就攤上這事乙嘀。” “怎么了破喻?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵虎谢,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我曹质,道長(zhǎng)婴噩,這世上最難降的妖魔是什么擎场? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮几莽,結(jié)果婚禮上迅办,老公的妹妹穿的比我還像新娘。我一直安慰自己章蚣,他們只是感情好站欺,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著纤垂,像睡著了一般矾策。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上洒忧,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天蝴韭,我揣著相機(jī)與錄音,去河邊找鬼熙侍。 笑死榄鉴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蛉抓。 我是一名探鬼主播庆尘,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼巷送!你這毒婦竟也來(lái)了驶忌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤笑跛,失蹤者是張志新(化名)和其女友劉穎付魔,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體飞蹂,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡几苍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了陈哑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妻坝。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惊窖,靈堂內(nèi)的尸體忽然破棺而出刽宪,到底是詐尸還是另有隱情,我是刑警寧澤界酒,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布圣拄,位于F島的核電站,受9級(jí)特大地震影響毁欣,放射性物質(zhì)發(fā)生泄漏庇谆。R本人自食惡果不足惜赁遗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望族铆。 院中可真熱鬧,春花似錦哭尝、人聲如沸哥攘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)逝淹。三九已至,卻和暖如春桶唐,著一層夾襖步出監(jiān)牢的瞬間栅葡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工尤泽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留欣簇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓坯约,卻偏偏與公主長(zhǎng)得像熊咽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闹丐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容