NSNotification
的便利性和內(nèi)存泄露風(fēng)險(xiǎn)
實(shí)現(xiàn)在兩個(gè)互不相關(guān)的模塊之間通信,NSNotification
是一個(gè)很好用的工具砌溺,但是覺(jué)得 NSNotification
的設(shè)計(jì)讓開(kāi)發(fā)者用起來(lái)不舒服红竭∮妊可以歸結(jié)為不方便使用和有內(nèi)存泄露的隱患問(wèn)題。
- 不方便使用
NSNotificationCenter
有以下的方法訂閱通知茵宪。
- (void)addObserver:(id)notificationObserver
selector:(SEL)notificationSelector
name:(NSString *)notificationName
object:(id)notificationSender
selector
的方式有兩個(gè)不好的地方最冰。
其一,訂閱和處理訂閱的邏輯分開(kāi)稀火,查看代碼的時(shí)候不直觀暖哨;
其二,需要再寫一個(gè) selector
凰狞,和絞盡腦子想合適的名字篇裁,即使有些時(shí)候名字并不重要。
總的來(lái)說(shuō)赡若,代碼不好組織和我懶达布。不過(guò)優(yōu)點(diǎn)還是有的,不容易發(fā)生內(nèi)存泄露逾冬。
- 內(nèi)存泄露的隱患
NSNotificationCenter
還提供一個(gè)方法實(shí)現(xiàn)訂閱黍聂。
- (id<NSObject>)addObserverForName:(NSString *)name
object:(id)obj
queue:(NSOperationQueue *)queue
usingBlock:(void (^)(NSNotification *note))block
處理訂閱的邏輯寫在 block
內(nèi),這樣一來(lái),selector
的缺點(diǎn)不存在了分冈,邏輯相關(guān)的代碼放在一塊圾另,不需要絞盡腦汁想方法名霸株。
但是雕沉!會(huì)有內(nèi)存泄露的風(fēng)險(xiǎn),下面的代碼有內(nèi)存泄露問(wèn)題去件。
id observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"Test1"
object:nil
queue:nil
usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"%@", self);
}];
NSNotificationCenter
強(qiáng)引用 observer
坡椒,block
要是強(qiáng)引用 self
,就會(huì)出現(xiàn)內(nèi)存泄露尤溜。因此要避免內(nèi)存引用倔叼,要在合適的地方取消訂閱以及弱引用 self
。
比較 selector
和 block
的方式宫莱,我傾向使用 block
的方案丈攒,寫起來(lái)非常便捷。于是乎授霸,方向只有一個(gè)巡验,解決內(nèi)存泄露的問(wèn)題,禁止在 block
內(nèi)強(qiáng)引用self
碘耳。
最佳實(shí)踐
將焦點(diǎn)放在 usingBlock
里显设,要是能改成 usingBlock:^(id self, NSNotification * _Nonnull note)
,其中 id self
是弱引用 self
辛辨,并在 block
的作用域內(nèi)屏蔽外部的 self
捕捂,從而實(shí)現(xiàn)將強(qiáng)引用的 self
替換成弱引用的 self
。
思路有了斗搞,就可以動(dòng)手封裝了指攒,將系統(tǒng)的方法封裝成帶弱引用的 self
的 block
。PLAPubSub
這個(gè)庫(kù)只是把 selecotr
封裝成 block
的調(diào)用方式僻焚,并沒(méi)有消除內(nèi)存泄露的風(fēng)險(xiǎn)允悦。我在這個(gè)庫(kù)(鏈接)的基礎(chǔ)上,改了他的代碼溅呢,解決了內(nèi)存泄露的問(wèn)題澡屡,核心代碼如下。
typedef void (^Handler)(id self, Event *event);
- (id)subscribe:(NSString *)name handler:(Handler)handler {
__weak __typeof__(self) weakSelf = self;
id observer = [[NSNotificationCenter defaultCenter] addObserverForName:name object:nil queue:nil usingBlock:^(NSNotification *note) {
GLEvent *event = [[GLEvent alloc] initWithName:eventName obj:note.object data:[note.userInfo objectForKey:kGLPubSubDataKey]];
handler(weakSelf, event);
}];
// 還要保存 observer咐旧,取消訂閱的時(shí)候需要用到 observer驶鹉,可以使用關(guān)聯(lián)對(duì)象的方法存放 observer。
return observer;
}
提醒一句铣墨,還是要正確使用室埋,在 dealloc
里記得取消訂閱,不然 NSNotificationCenter
不會(huì)釋放 observer
。
NSTimer
的內(nèi)存泄露風(fēng)險(xiǎn)
NSTimer 使用不當(dāng)也是非常容易有內(nèi)存泄露的問(wèn)題姚淆。官方文檔給出了 NSTimer
的 API孕蝉,有一個(gè)特點(diǎn)。
+ scheduledTimerWithTimeInterval:invocation:repeats:
+ scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
+ timerWithTimeInterval:invocation:repeats:
+ timerWithTimeInterval:target:selector:userInfo:repeats:
很明顯腌逢,需要有一個(gè) invocation
或者 target
和 selector
的組合降淮。而這是容易帶來(lái)風(fēng)險(xiǎn)的地方。
self.timer = [NSTimer scheduledTimerWithTimeInterval:5
target:self
selector:@selector(test)
userInfo:nil
repeats:NO];
這段代碼已經(jīng)產(chǎn)生了內(nèi)存泄露搏讶,只有在5秒后才解除風(fēng)險(xiǎn)佳鳖,如果把 repeats
改為 YES
,永遠(yuǎn)解除不了風(fēng)險(xiǎn)媒惕。有一個(gè)地方值得注意系吩,無(wú)論 self
對(duì) timer
是強(qiáng)引用還是若引用,都改變不了什么妒蔚。
要弄清楚問(wèn)題穿挨,需要了解 self
、NSTimer
和 RunLoop
是之間是如何引用的肴盏。
NSTimer
和RunLoop
的關(guān)系
定時(shí)器的功能是借助RunLoop
實(shí)現(xiàn)的科盛,在NSTimer
被加到RunLoop
的時(shí)候,RunLoop
會(huì)強(qiáng)引用NSTimer
叁鉴。
在被移除RunLoop
的時(shí)候(類方法創(chuàng)建且不是重復(fù)或者調(diào)用invalidate
)土涝,RunLoop
就不會(huì)再?gòu)?qiáng)引用NSTimer
。self
和NSTimer
的關(guān)系
self
對(duì)NSTimer
的引用可強(qiáng)可弱幌墓。反過(guò)來(lái)但壮,NSTimer
對(duì)self
只能是強(qiáng)引用鹅很。
再來(lái)看上一段代碼约郁,在前5秒,RunLoop
對(duì) NSTimer
是強(qiáng)引用黔夭,而 NSTimer
對(duì) self
只能是強(qiáng)引用胳施,所以無(wú)論 self
對(duì) NSTimer
是強(qiáng)還是弱引用溯祸,都不能析構(gòu) self
,從而有了內(nèi)存泄露的問(wèn)題舞肆。
最佳實(shí)踐
前面也提到焦辅,個(gè)人不喜歡 selector
和原因,在這里也是希望能通過(guò)封裝椿胯,把 selector
封裝成 block
的方式筷登。我前同事 callMeWhy 開(kāi)源了一個(gè)庫(kù) NSWeakTimer
(鏈接),完美解決這個(gè)問(wèn)題哩盲,代碼也非常簡(jiǎn)單前方,我給大家講解一下他的思路狈醉。
RunLoop
對(duì) Timer
的引用只能是強(qiáng)引用,Timer
對(duì) self
也只能是強(qiáng)引用惠险,問(wèn)題的解決方法是讓 Timer
對(duì) self
弱引用苗傅,所以加入第三方,Timer
對(duì)第三方強(qiáng)引用班巩, 第三方對(duì) self
是弱引用渣慕,其余的地方能用弱引用都用弱引用。
最后提醒
我司開(kāi)發(fā)沒(méi)有正確使用 NOtification
和 NSTimer
導(dǎo)致出現(xiàn)了一些奇怪的問(wèn)題趣竣,所以務(wù)必在 dealloc
取消訂閱和停止計(jì)時(shí)器摇庙,不然 block
還是會(huì)執(zhí)行旱物,只是 self
已經(jīng)析構(gòu)了遥缕。