主要內容包含如下:
- 實現原理(結構設計、通知如何存儲的、name&observer&SEL之間的關係等)
- 通知的發送時同步的,還是異步的
- NSNotificationCenter接受消息和發送消息是在一個線程裏嗎?如何異步發送消息
- NSNotificationQueue是異步還是同步發送?在哪個線程響應
- NSNotificationQueue和runloop的關係
- 如何保證通知接收的線程在主線程
- 頁面銷燬時不移除通知會崩潰嗎
- 多次添加同一個通知會是什麼結果?多次移除通知呢
- 下面的方式能接收到通知嗎?爲什麼
// 發送通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];
// 接收通知
[NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];
在解釋這些內容之前 強烈建議認真研讀一下這篇 一文全解iOS通知機制(經典收藏)文章 瞭解一下大概 所有的問題就迎刃而解了.
實現原理(結構設計、通知如何存儲的、name&observer&SEL之間的關係等
首先通知中心結構大概分爲如下幾個類
-
NSNotification
通知的模型 name、object、userinfo. -
NSNotificationCenter
通知中心 負責發送NSNotification
-
NSNotificationQueue
通知隊列 負責在某些時機觸發 調用NSNotificationCenter
通知中心post
通知
通知是結構體通過雙向鏈表進行數據存儲
// 根容器,NSNotificationCenter持有
typedef struct NCTbl {
Observation *wildcard; /* 鏈表結構,保存既沒有name也沒有object的通知 */
GSIMapTable nameless; /* 存儲沒有name但是有object的通知 */
GSIMapTable named; /* 存儲帶有name的通知,不管有沒有object */
...
} NCTable;
// Observation 存儲觀察者和響應結構體,基本的存儲單元
typedef struct Obs {
id observer; /* 觀察者,接收通知的對象 */
SEL selector; /* 響應方法 */
struct Obs *next; /* Next item in linked list. */
...
} Observation;
主要是以key
value
的形式存儲,這裏需要重點強調一下 通知以 name
和object
兩個緯度來存儲相關通知內容,也就是我們添加通知的時候傳入的兩個不同的方法.
簡單理解name
&observer
&SEL
之間的關係就是name
作爲key
, observer
作爲觀察者對象,當合適時機觸發就會調用observer
的SEL
.這基本很簡單,如果覺得我說的不準確可以看下文章開頭的文章.
通知的發送時同步的,還是異步的
同步發送.因爲要調用消息轉發.所謂異步,指的是非實時發送而是在合適的時機發送,並沒有開啓異步線程.
NSNotificationCenter接受消息和發送消息是在一個線程裏嗎?如何異步發送消息
是的, 異步線程發送通知則響應函數也是在異步線程.
異步發送通知可以開啓異步線程發送即可.
NSNotificationQueue是異步還是同步發送?在哪個線程響應
// 表示通知的發送時機
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, // runloop空閒時發送通知
NSPostASAP = 2, // 儘快發送,這種時機是穿插在每次事件完成期間來做的
NSPostNow = 3 // 立刻發送或者合併通知完成之後發送
};
NSPostWhenIdle | NSPostASAP | NSPostNow | |
---|---|---|---|
NSPostingStyle | 異步發送 | 異步發送 | 同步發送 |
NSNotificationCenter
都是同步發送的,而這裏介紹關於NSNotificationQueue
的異步發送,從線程的角度看並不是真正的異步發送,或可稱爲延時發送,它是利用了runloop
的時機來觸發的.
異步線程發送通知則響應函數也是在異步線程,主線程發送則在主線程.
NSNotificationQueue和runloop的關係
NSNotificationQueue
依賴runloop
. 因爲通知隊列要在runloop回調的某個時機調用通知中心發送通知.從下面的枚舉值就能看出來
// 表示通知的發送時機
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, // runloop空閒時發送通知
NSPostASAP = 2, // 儘快發送,這種時機是穿插在每次事件完成期間來做的
NSPostNow = 3 // 立刻發送或者合併通知完成之後發送
};
如何保證通知接收的線程在主線程
如果想在主線程響應異步通知的話可以用如下兩種方式
1.系統接受通知的API指定隊列
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block
2.NSMachPort
的方式 通過在主線程的runloop中添加machPort,設置這個port的delegate,通過這個Port其他線程可以跟主線程通信,在這個port的代理回調中執行的代碼肯定在主線程中運行,所以,在這裏調用NSNotificationCenter發送通知即可
頁面銷燬時不移除通知會崩潰嗎?
iOS9.0之前,會crash,原因:通知中心對觀察者的引用是unsafe_unretained,導致當觀察者釋放的時候,觀察者的指針值並不爲nil,出現野指針.
iOS9.0之後,不會crash,原因:通知中心對觀察者的引用是weak。
多次添加同一個通知會是什麼結果?多次移除通知呢
多次添加同一個通知,會導致發送一次這個通知的時候,響應多次通知回調。 多次移除通知不會產生crash。
下面的方式能接收到通知嗎?爲什麼
// 發送通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];
// 接收通知
[NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];
不能
首先我們看下通知中心存儲通知觀察者的結構
// 根容器,NSNotificationCenter持有
typedef struct NCTbl {
Observation *wildcard; /* 鏈表結構,保存既沒有name也沒有object的通知 */
GSIMapTable nameless; /* 存儲沒有name但是有object的通知 */
GSIMapTable named; /* 存儲帶有name的通知,不管有沒有object */
...
} NCTable;
// Observation 存儲觀察者和響應結構體,基本的存儲單元
typedef struct Obs {
id observer; /* 觀察者,接收通知的對象 */
SEL selector; /* 響應方法 */
struct Obs *next; /* Next item in linked list. */
...
} Observation;
nameless
與named
的具體數據結構如下:
當添加通知監聽的時候,我們傳入了name
和object
,所以,觀察者的存儲鏈表是這樣的:
named
表:key(name)
: value
->key(object)
: value(Observation)
因此在發送通知的時候,如果只傳入name
而並沒有傳入object
,是找不到Observation
的,也就不能執行觀察者回調.
總結
今天又重新認識了iOS中的通知中心,希望大家經常溫故而知新.
推薦
- 更多:iOS面試題大全
- 更多:《BAT面試答案文集.PDF》,獲取可加iOS技術交流圈:937 194 184。
收錄:原文地址