NotificationCenter類實(shí)現(xiàn)原理
正文
NotificationCenter是一個(gè)系統(tǒng)組件,它負(fù)責(zé)協(xié)調(diào)和管理事件的通知和響應(yīng)。它的基本原理是基于觀察者模式!而 Apple 對(duì)其是閉源的,因此無(wú)法查看 NotificationCenter 的源碼,但是可以通過(guò)分析開(kāi)源的 Swift 來(lái)理解 NotificationCenter 的實(shí)現(xiàn),以下是一個(gè)簡(jiǎn)化的實(shí)現(xiàn):
簡(jiǎn)單實(shí)現(xiàn)
1、首先定義一個(gè)NotificationCenter類定義
class RYNotificationCenter { private init(){} static let `default` = RYNotificationCenter() private var observers: [RYNotificationObserver] = [] }
定義了一個(gè)單例,用于在整個(gè)程序中共享,observers
數(shù)組用來(lái)存儲(chǔ)已經(jīng)注冊(cè)的所有觀察者。
2、然后定義一個(gè)觀察者對(duì)象
觀察者對(duì)象用來(lái)封裝具體的觀察者的信息。
class RYNotificationObserver { var name: String var block: (Notification) -> Void init(name: String, block: @escaping (Notification) -> Void) { self.name = name self.block = block } }
3、在NotificationCenter中添加注冊(cè)觀察者的方法
func addObserver(name: String, block: @escaping (Notification) -> Void) -> RYNotificationObserver { let observer = RYNotificationObserver(name: name, block: block) observers.append(observer) return observer }
addObserver
方法用于注冊(cè)觀察者。在這個(gè)實(shí)現(xiàn)中,我們創(chuàng)建了一個(gè)新 RYNotificationObserver
對(duì)象并將其添加到 observers
數(shù)組。這個(gè)方法返回觀察者對(duì)象,以便稍后從 NotificationCenter
中移除。
4、在 NotificationCenter 中添加發(fā)送通知的方法
/// 發(fā)送通知的本質(zhì)是利用了觀察者模式 /// 讓觀察者數(shù)組執(zhí)行閉包中的代碼 func post(name: String, userInfo: [AnyHashable: Any]? = nil) { let notification = Notification(name: Notification.Name(name), userInfo: userInfo) observers .filter({ $0.name == name }) .forEach { $0.block(notification) } }
post
方法用來(lái)發(fā)送通知,它接受通知名以及可選的userInfo
字典。同時(shí)參數(shù)都包裝在Notification
對(duì)象中,然后遍歷 observers
數(shù)組。如果觀察者的名稱和通知名稱匹配,我們將執(zhí)行保存的block
。
5、在NotificationCenter中添加移除通知者的方法
func removeObserver(_ observer: RYNotificationObserver) { if let index = observers.firstIndex(where: { $0 === observer }) { observers.remove(at: index) } }
removeObserver
方法用于移除觀察者。它接受一個(gè)觀察者對(duì)象并從 observers
數(shù)組中移除它。
NotificationCenter的源碼分析
普遍來(lái)說(shuō),現(xiàn)在分析 NotificationCenter
的源碼,一般是 github.com/gnustep/lib… ,這是在 gnustep 庫(kù)的源碼中,它和官方的具體實(shí)現(xiàn)肯定是有差異的,但是可以以它為參考的對(duì)象,在這里通知的源碼使用了三個(gè)主要的類:
- NSNotification
- NSNotificationCenter
- NSNotificationQueue
NSNotificationCenter 實(shí)現(xiàn)
用于在觀察者和發(fā)送者之間發(fā)送通知,這是核心類,它的方法和Objective-C是一致的,使用 **addObserver:selector:name:object:
方法來(lái)添加觀察者,但是它在內(nèi)部使用了C語(yǔ)言實(shí)現(xiàn)鏈表的數(shù)據(jù)結(jié)構(gòu) Obs
存儲(chǔ)觀察者相關(guān)的信息:
typedef struct Obs { id observer; /* Object to receive message. */ SEL selector; /* Method selector. */ struct Obs *next; /* Next item in linked list. */ int retained; /* Retain count for structure. */ struct NCTbl *link; /* Pointer back to chunk table */ } Observation;
而在 postNotificationName:object:userInfo:
方法執(zhí)行的時(shí)候會(huì)通過(guò)通知名找到封裝好的 Obs
觀察者,然后執(zhí)行相應(yīng)的方法:
- (void) postNotificationName: (NSString*)name object: (id)object userInfo: (NSDictionary*)info { // 先封裝好notification GSNotification *notification; notification = (id)NSAllocateObject(concrete, 0, NSDefaultMallocZone()); notification->_name = [name copyWithZone: [self zone]]; notification->_object = [object retain]; notification->_info = [info retain]; [self _postAndRelease: notification]; } // 然后調(diào)用觀察者的selector方法 - (void) _postAndRealse: (NSNotification*)notification { ...... [o->observer performSelector: o->selector withObject: notification]; ...... }
當(dāng)然,要將封裝好的 notification
,作為參數(shù)傳遞給觀察者需要執(zhí)行的 selector
。
NSNotification 實(shí)現(xiàn)
那么 Notifiation 呢?它是一個(gè)包含了通知的名稱、發(fā)送者對(duì)象以及用戶信息字典的不可變對(duì)象。
- (id) initWithCoder: (NSCoder*)aCoder { NSString *name; id object; NSDictionary *info; id n; [aCoder decodeValueOfObjCType: @encode(id) at: &name]; [aCoder decodeValueOfObjCType: @encode(id) at: &object]; [aCoder decodeValueOfObjCType: @encode(id) at: &info]; n = [NSNotification notificationWithName: name object: object userInfo: info]; RELEASE(name); RELEASE(object); RELEASE(info); DESTROY(self); return RETAIN(n); }
NSNotificationQueue 的實(shí)現(xiàn)
最后是 NSNotificationQueue 的實(shí)現(xiàn),它是一個(gè)用于管理通知發(fā)送的隊(duì)列,可以按照特定的發(fā)送模式(例如合并相同的通知或按發(fā)送順序)將通知排隊(duì)。
- (void) enqueueNotification: (NSNotification*)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask: (NSUInteger)coalesceMask forModes: (NSArray*)modes { if (modes == nil) { modes = defaultMode; } if (coalesceMask != NSNotificationNoCoalescing) { [self dequeueNotificationsMatching: notification coalesceMask: coalesceMask]; } switch (postingStyle) { case NSPostNow: { NSString *mode; mode = [[NSRunLoop currentRunLoop] currentMode]; if (mode == nil || [modes indexOfObject: mode] != NSNotFound) { [_center postNotification: notification]; } } break; case NSPostASAP: add_to_queue(_asapQueue, notification, modes, _zone); break; case NSPostWhenIdle: add_to_queue(_idleQueue, notification, modes, _zone); break; } }
當(dāng)使用 NSNotificationQueue
的時(shí)候,就不需要我們手動(dòng)發(fā)送 Notification 了,NSNotificationQueue
會(huì)自動(dòng)幫我們發(fā)送,在上述代碼中,如果是 NSPostNow
,那么通知會(huì)立馬被發(fā)送,否則就先加入隊(duì)列中:_asapQueue
或者 _idleQueue
,然后在合適的時(shí)候執(zhí)行隊(duì)列中的通知,比如:
void GSPrivateNotifyIdle(NSString *mode) { NotificationQueueList *item; for (item = currentList(); item; item = item->next) { if (item->queue) { notify(item->queue->_center, item->queue->_idleQueue, mode, item->queue->_zone); } } }
問(wèn)題:如果NotificationCenter
添加的觀察者是self,會(huì)造成循環(huán)引用嗎?
答案是:不會(huì)!
NotificationCenter 對(duì)觀察者的引用方式是弱引用(weak),而不是強(qiáng)持有(strong)。因此,當(dāng)一個(gè)對(duì)象被銷毀時(shí),它的 deinit
方法會(huì)被調(diào)用,即使它是一個(gè)觀察者。所以即使我們不在 deinit
方法中添加移除 self 的操作也是可以的,因?yàn)?NotificationCenter 并沒(méi)有對(duì)觀察者強(qiáng)持有。
問(wèn)題:如果 NotificationCenter
添加的是 block ,而 block 強(qiáng)持有了 self ,這會(huì)造成循環(huán)引用嗎?
答案是:會(huì)!
從iOS 9開(kāi)始,如果使用了基于 block 的觀察者,那么就需要去小心觀察者的生命周期了,因?yàn)?code>NotificationCenter 對(duì)添加的 block 是強(qiáng)持有的,正如上述簡(jiǎn)單實(shí)現(xiàn)中的那樣,它對(duì)閉包中捕獲的變量就也是強(qiáng)持有的,所以為了避免這種現(xiàn)象,需要確保使用 [weak self]
來(lái)捕獲列表。
在實(shí)際使用的時(shí)候,由于編碼慣性,可能會(huì)在 deinit
方法中移除基于 block 的觀察者以解決該問(wèn)題:
class ViewController: UIViewController { private weak var observer: NSObjectProtocol! func addObserver() { observer = NotificationCenter.default.addObserver(forName: NSNotification.Name("test"), object: nil, queue: OperationQueue.main) { _ in self.view.backgroundColor = UIColor.white } } deinit { NotificationCenter.default.removeObserver(observer!) } }
但是在這種情況下, deinit
方法并不會(huì)執(zhí)行! 原因就是 NotificationCenter
持有了 block, 也間接持有了 self,而 NotificationCenter
是一個(gè)單例,所以這種持有關(guān)系是一直存在的,導(dǎo)致了 deinit
方法并不會(huì)執(zhí)行!
問(wèn)題:觀察者的 selector
執(zhí)行的線程和發(fā)送通知的線程有關(guān)嗎?
答案是:正相關(guān)!
從上文中的簡(jiǎn)單實(shí)現(xiàn)以及GNU的源碼中基本可以看出結(jié)論了。添加觀察者的線程并沒(méi)有什么影響,而發(fā)送通知的線程,其實(shí)就是調(diào)用方法執(zhí)行的線程,所以兩者是在同一線程執(zhí)行的。
func addObserver() { NotificationCenter.default.addObserver(self, selector: #selector(click), name: NSNotification.Name.init("test"), object: nil) DispatchQueue.global().async { NotificationCenter.default.post(name: NSNotification.Name.init("test"), object: nil) NSLog("curretThread1: \(Thread.current)") } } @objc func click() { NSLog("curretThread2: \(Thread.current)") } // curretThread2: <NSThread: 0x600001358240>{number = 6, name = (null)} // curretThread1: <NSThread: 0x600001358240>{number = 6, name = (null)}
同時(shí)還需要注意的就是通知發(fā)送,然后 selector
被執(zhí)行,這個(gè)過(guò)程其實(shí)本質(zhì)上是一個(gè)觀察者模式的實(shí)現(xiàn)方式,同時(shí),它也是同步執(zhí)行的,再執(zhí)行完發(fā)送消息的方法后就會(huì)去尋找對(duì)應(yīng)的 Observer
,找到之后就執(zhí)行相應(yīng)的 selector
,執(zhí)行完之后,發(fā)送消息的方法才執(zhí)行完畢了。
所以發(fā)送通知和監(jiān)聽(tīng)通知執(zhí)行方法的核心是:相同線程執(zhí)行 且 同步執(zhí)行。
以上就是NotificationCenter類實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于NotificationCenter 原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Swift類型創(chuàng)建之自定義一個(gè)類型詳解
這篇文章主要介紹了Swift類型創(chuàng)建之自定義一個(gè)類型詳解,本文講解了自定義原型、實(shí)現(xiàn)默認(rèn)值、支持基本布爾型初始化、支持Bool類型判斷、支持兼容各們各派的類型、完善OCBool的布爾基因體系等內(nèi)容,需要的朋友可以參考下2015-05-05Swift設(shè)計(jì)思想Result<T>與Result<T,?E:?Error>類型解析
這篇文章主要為大家介紹了Swift設(shè)計(jì)思想Result<T>與Result<T,?E:?Error>的類型示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Swift如何調(diào)用Objective-C的可變參數(shù)函數(shù)詳解
這篇文章主要給大家介紹了關(guān)于Swift如何調(diào)用Objective-C的可變參數(shù)函數(shù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用swift具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03Ubuntu 16.04上安裝 Swift 3.0及問(wèn)題解答
本文給大家分享的是在Ubuntu系統(tǒng)中安裝 Swift 3.0的方法和步驟,以及安裝過(guò)程中有可能遇到的問(wèn)題的解答,這里推薦給小伙伴們,希望大家能夠喜歡2016-07-07Swift仿微信語(yǔ)音通話最小化時(shí)后的效果實(shí)例代碼
這篇文章主要介紹了Swift仿微信語(yǔ)音通話最小化時(shí)后的效果的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03Swift實(shí)現(xiàn)表格視圖單元格單選(1)
這篇文章主要為大家詳細(xì)介紹了Swift實(shí)現(xiàn)表格視圖單元格單選,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01