NSNotification是iOS中一個(gè)調(diào)度消息通知的類,采用單例設(shè)計(jì)模式,在開發(fā)中實(shí)現(xiàn)傳值、回調(diào)等。在iOS中,NSNotification是使用觀察者模式來實(shí)現(xiàn)用于跨層傳遞消息。
創(chuàng)新互聯(lián)公司主要從事成都網(wǎng)站建設(shè)、成都做網(wǎng)站、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)涪城,十余年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18982081108
NSNotification包含了一些用于向其他對(duì)象發(fā)送通知的必要信息,包括名稱、對(duì)象和可選字典,并由NSNotificationCenter或NSDistributedNotificationCenter的實(shí)例進(jìn)行發(fā)送。name是標(biāo)識(shí)通知的標(biāo)記、object是保存發(fā)送通知的對(duì)象、userinfo存儲(chǔ)其他相關(guān)對(duì)象。 這里主要注意的是:NSNotification對(duì)象是不可變的。
可以使用 notificationWithName:object: 或 notificationWithName:object:userInfo: 創(chuàng)建通知對(duì)象。但實(shí)際開發(fā)中,一般是直接使用NSNotificationCenter調(diào)用 postNotificationName:object: 或 postNotificationName:object:userInfo: ,這兩個(gè)類方法會(huì)在內(nèi)部直接創(chuàng)建NSNotification對(duì)象,并發(fā)出通知。
??從官網(wǎng)文檔可知,NSNotification是不能直接實(shí)例化的,如果用init方法進(jìn)行實(shí)例化時(shí),會(huì)引發(fā)異常。還有需要注意的是如果我們自己去實(shí)現(xiàn)構(gòu)造方法時(shí),不能在super上調(diào)用init方法。
NSNotificationCenter提供了一套機(jī)制來發(fā)送通知,每個(gè)運(yùn)行中的應(yīng)用程序都有一個(gè)defaultCenter通知中心,我們可以創(chuàng)建新的通知中心來組織特定上下文中的通信。??NSNotificationCenter暴露給外部的字段只有一個(gè)defaultCenter,并且該字段是只讀的,暴露出來的方法分為三種:添加、移除通知觀察者和發(fā)出通知。詳細(xì)如下表所示:
postNotificationName:object:
postNotificationName:object:userInfo: |
相關(guān)說明:
簡單理解為:通知中心的緩沖區(qū)。盡管通知中心已經(jīng)分發(fā)通知,但放置到隊(duì)列中的通知可能會(huì)延遲,直到runloop結(jié)束或者runloop空閑時(shí)才發(fā)送。如果有多個(gè)相同的通知,NSNotificationQueue會(huì)將其進(jìn)行合并,以便在發(fā)布多個(gè)通知的情況下只發(fā)送一個(gè)通知。
??通知隊(duì)列按照先進(jìn)先出(FIFO)的順序維護(hù)通知。當(dāng)一個(gè)通知移動(dòng)到隊(duì)列的前面時(shí),隊(duì)列將它發(fā)送到通知中心,然后再將通知分派給所有注冊(cè)為觀察者的對(duì)象。每個(gè)線程都有一個(gè)默認(rèn)的通知隊(duì)列,該隊(duì)列與流程的默認(rèn)通知中心相關(guān)聯(lián)。我們也可以創(chuàng)建自己的通知隊(duì)列。
??和NSNotificationCenter一樣,NSNotificationQueue也只暴露了一個(gè)字段:defaultQueue,返回當(dāng)前線程的默認(rèn)通知隊(duì)列。方法分為:創(chuàng)建通知隊(duì)列和管理通知。詳細(xì)說明如下表所示:
dequeueNotificationsMatching:coalesceMask:
enqueueNotification:postingStyle:coalesceMask:forModes: |
方法相關(guān)說明:
在上面的方法中,需要注意的2個(gè)常量,相關(guān)說明如下:
NSNotificationCenter定義了兩個(gè)Table,同時(shí)為了封裝觀察者信息,也定義了Observation保存觀察者信息。他們的結(jié)構(gòu)體可以簡化如下所示:
在NSNotificationCenter內(nèi)部一共保存了兩張表,一張用于保存添加觀察者的時(shí)候傳入的NotificationName的情況;一張用于保存添加觀察者的時(shí)候沒有傳入NotificationCenter的情況,詳細(xì)分析如下:
在Named Table中,NotificationName作為表的key,因?yàn)槲覀冊(cè)谧?cè)觀察者的時(shí)候是可以傳入一個(gè)object參數(shù)用于只監(jiān)聽該對(duì)象發(fā)出的通知,并且一個(gè)通知可以添加多個(gè)觀察者,所以還需要一張表用來保存object和observe的對(duì)應(yīng)關(guān)系。這張表的key、value分別是以object為key,observe為value。所以對(duì)于Named Table,最終的結(jié)構(gòu)為:
Named Table
特別說明:在實(shí)際開發(fā)中,我們經(jīng)常將object參數(shù)傳nil,這個(gè)時(shí)候系統(tǒng)會(huì)根據(jù)nil自動(dòng)產(chǎn)生一個(gè)key。相當(dāng)于這個(gè)key對(duì)應(yīng)的value(鏈表)保存的就是對(duì)于當(dāng)前NotificationName沒有傳入object的所有觀察者。當(dāng)NotificationName被發(fā)送時(shí),在鏈表中的觀察者都會(huì)收到通知。
UNamed Table結(jié)構(gòu)比Named Table簡單得多。因?yàn)闆]有NotificationName作為key。這里直接就以object為key,比Named Table少了一層Table嵌套。
UnNamed Table
如果在注冊(cè)觀察者時(shí)沒有傳入NotificationName,同時(shí)沒有傳入object,所有的系統(tǒng)通知都會(huì)發(fā)送到注冊(cè)的對(duì)象里。
首先在初始化NSNotificationCenter時(shí)會(huì)創(chuàng)建一個(gè)對(duì)象,這個(gè)對(duì)象里面保存了Named Table、UNamed Table和其他信息。
在沒有傳入NotificationName的情況和上面的過程類似,只不過是直接根據(jù)object去對(duì)應(yīng)的鏈表而已。如果既沒有傳入NotificationName,也沒有傳入object,則這個(gè)觀察者會(huì)添加到wildcard鏈表中。
發(fā)送通知一般是調(diào)用 postNotificationName:object:userInfo: 方法來實(shí)現(xiàn)。該方法內(nèi)部會(huì)實(shí)例化一個(gè)NSNotification來保存?zhèn)魅氲母鞣N參數(shù),包括name、object和userinfo。
??發(fā)送通知的流程總體來說就是根據(jù)NotificationName查找到對(duì)應(yīng)的Observer鏈表,然后遍歷整個(gè)鏈表,給每個(gè)Observer結(jié)點(diǎn)中保存的對(duì)象及SEL,來向?qū)ο蟀l(fā)送消息。具體流程如下:
這個(gè)方式也就能說明,發(fā)送通知的線程和接收通知的線程都是同一個(gè)線程。
NSNotification和線程同步之間是什么關(guān)系呢?先看下官方文檔的說明:
翻譯過來意思為:
更多關(guān)于NSNotification與線程之間的關(guān)系,請(qǐng)閱讀下面的文章: iOS開發(fā) - NSNotification和線程相關(guān)
總的來說,NSNotification的三個(gè)相關(guān)類的作用,可以用下圖進(jìn)行歸納總結(jié)。
總結(jié)
用于異步發(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 未開啟則無效。
把通知添加到隊(duì)列等待發(fā)送,同時(shí)提供了一些附加條件供開發(fā)者選擇,如:什么時(shí)候發(fā)送通知、如何合并通知等,系統(tǒng)給了如下定義。
可以,因?yàn)?notificationcenter 對(duì)觀察者的引用是 weak ,當(dāng)觀察者釋放的時(shí)候,觀察者的指針值被置為 nil
會(huì)調(diào)用多次 observer 的 action 。多次移除沒有任何影響。
NCTable 結(jié)構(gòu)體中核心的三個(gè)變量: wildcard 、 named 、 nameless ,在源碼中直接用宏定義表示了: WILDCARD 、 NAMELESS 、 NAMED 。
? NAMED 是個(gè)宏,表示名為 named 的字典。如果通知的 name 存在,則以 name 為 key 從 named 字典中取出值 n (這個(gè) n 其實(shí)被 MapNode 包裝了一層,便于理解這里直接認(rèn)為沒有包裝),這個(gè) n 還是個(gè)字典。
? n 不存在,則先取緩存,如果緩存沒有則新建一個(gè) map
? n 存在則把值取出來賦值給 m
? 然后以 object 為 key ,從字典中取出對(duì)應(yīng)的值,這個(gè)值就是 Observation 類型的鏈表,然后把剛開始創(chuàng)建的 Observation 對(duì)象 o 存儲(chǔ)進(jìn)去。
? 以 object 為 key ,從 nameless 字典中取出對(duì)應(yīng)的 value , value 是個(gè)鏈表結(jié)構(gòu)。
? 不存在則新建鏈表,并存到 map 中
? 存在則把值接到鏈表的節(jié)點(diǎn)上
如果注冊(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 為 key ,從 nameless 字典中取出 value ,此 value 是個(gè) Observation 類型的鏈表。接著把創(chuàng)建的 Observation 類型的對(duì)象 o 存儲(chǔ)到鏈表中。只存在 object 時(shí)存儲(chǔ)只有一層,那就是 object 和 Observation 對(duì)象之間的映射。
這種情況直接把 Observation 對(duì)象存放在了 Observation *wildcard 鏈表結(jié)構(gòu)中。
發(fā)送通知的核心邏輯比較簡單,基本上就是查找和調(diào)用響應(yīng)方法,從三個(gè)存儲(chǔ)容器中: named 、 nameless 、 wildcard 去查找對(duì)應(yīng)的 Observation 對(duì)象,然后通過 performSelector :逐一調(diào)用響應(yīng)方法,這就完成了發(fā)送流程。
主要做了三件事:查找通知、發(fā)送、釋放資源。
? 通過 name object 從 named 、 nameless 、 wildcard 表中查找對(duì)應(yīng)的通知(保存了 observer 和 sel )。
? 執(zhí)行發(fā)送,即調(diào)用 performSelector 執(zhí)行響應(yīng)方法,從這里可以看出是同步的。
? 釋放 notification 對(duì)象。
因?yàn)椴檎視r(shí)做了這個(gè)鏈表的遍歷,所以刪除時(shí)會(huì)把重復(fù)的通知全都刪除掉
查找時(shí)仍然以 name 和 object 為準(zhǔn),再加上 observer 做區(qū)分。
上面介紹的 NSNotificationCenter 都是同步發(fā)送的,接受消息和發(fā)送消息是在一個(gè)線程里。這里介紹關(guān)于 NSNotificationQueue 的異步發(fā)送,通過 NSNotificationQueue 將通知添加到隊(duì)列當(dāng)中,立即將控制權(quán)返回給調(diào)用者,在合適的時(shí)機(jī)發(fā)送通知,從而不會(huì)阻塞當(dāng)前的調(diào)用。從線程的角度看并不是真正的異步發(fā)送,或可稱為延時(shí)發(fā)送,它是利用了 runloop 的時(shí)機(jī)來觸發(fā)的,所以如果在其他子線程使用 NSNotificationQueue ,需要開啟 runloop 。由于最終還是通過 NSNotificationCenter 進(jìn)行發(fā)送通知,所以從這個(gè)角度講它還是同步的。所謂異步,指的是非實(shí)時(shí)發(fā)送而是在合適的時(shí)機(jī)發(fā)送,并沒有開啟異步線程。
NSPostingStyle 和 coalesceMask 在上面的類結(jié)構(gòu)中有介紹。 modes 這個(gè)就和 runloop 有關(guān)了,指的是 runloop 的 mode 。
? 根據(jù) coalesceMask 參數(shù)判斷是否合并通知
? 接著根據(jù) postingStyle 參數(shù),判斷通知發(fā)送的時(shí)機(jī)
? runloop 立即回調(diào)通知方法,同步發(fā)送
? runloop 在執(zhí)行 timer 事件或 sources 事件的時(shí)候回調(diào)通知方法,異步發(fā)送
? runloop 空閑的時(shí)候回調(diào)通知方法,異步發(fā)送
runloop 觸發(fā)某個(gè)時(shí)機(jī),調(diào)用 GSPrivateNotifyASAP() 和 GSPrivateNotifyIdle() 方法,這兩個(gè)方法最終都調(diào)用了 notify() 方法。 notify() 所做的事情就是調(diào)用 NSNotificationCenter 的 postNotification: 進(jìn)行發(fā)送通知。
異步線程發(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 。
1、首先將 epub 文件解壓后得到其資源文件包,其中會(huì)包含相應(yīng)的文件夾。
2、其次通過 OEBPS 文件夾中的資源文件提取所需的數(shù)據(jù)并進(jìn)行拼裝后渲染,包含了文件的解壓縮和通過 touchXML 對(duì) xml 數(shù)據(jù)的解析和寫入。
3、最后對(duì) xml 解析獲取到節(jié)點(diǎn)內(nèi)容并保存,遍歷數(shù)據(jù)數(shù)組找到其中所需的節(jié)點(diǎn),將其遍歷節(jié)點(diǎn)得到所需屬性的 name 和 value 作為字典對(duì)象填充至模型。