NSNotification 和 NStimer 的最佳實(shí)踐

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
比較 selectorblock 的方式宫莱,我傾向使用 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)的方法封裝成帶弱引用的 selfblockPLAPubSub 這個(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 或者 targetselector 的組合降淮。而這是容易帶來(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)題穿挨,需要了解 selfNSTimerRunLoop 是之間是如何引用的肴盏。

  • NSTimerRunLoop 的關(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

  • selfNSTimer 的關(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 是弱引用渣慕,其余的地方能用弱引用都用弱引用。

引用關(guān)系圖

最后提醒

我司開(kāi)發(fā)沒(méi)有正確使用 NOtificationNSTimer 導(dǎo)致出現(xiàn)了一些奇怪的問(wèn)題趣竣,所以務(wù)必dealloc 取消訂閱和停止計(jì)時(shí)器摇庙,不然 block 還是會(huì)執(zhí)行旱物,只是 self 已經(jīng)析構(gòu)了遥缕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宵呛,隨后出現(xiàn)的幾起案子单匣,更是在濱河造成了極大的恐慌,老刑警劉巖宝穗,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件户秤,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡逮矛,警方通過(guò)查閱死者的電腦和手機(jī)鸡号,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)须鼎,“玉大人鲸伴,你說(shuō)我怎么就攤上這事〗兀” “怎么了汞窗?”我有些...
    開(kāi)封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)赡译。 經(jīng)常有香客問(wèn)我仲吏,道長(zhǎng),這世上最難降的妖魔是什么蝌焚? 我笑而不...
    開(kāi)封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任裹唆,我火速辦了婚禮,結(jié)果婚禮上只洒,老公的妹妹穿的比我還像新娘许帐。我一直安慰自己,他們只是感情好红碑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開(kāi)白布舞吭。 她就那樣靜靜地躺著泡垃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪羡鸥。 梳的紋絲不亂的頭發(fā)上蔑穴,一...
    開(kāi)封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音惧浴,去河邊找鬼存和。 笑死,一個(gè)胖子當(dāng)著我的面吹牛衷旅,可吹牛的內(nèi)容都是我干的捐腿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼柿顶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼茄袖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起嘁锯,我...
    開(kāi)封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宪祥,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后家乘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蝗羊,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年仁锯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耀找。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡业崖,死狀恐怖野芒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情腻要,我是刑警寧澤复罐,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站雄家,受9級(jí)特大地震影響效诅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜趟济,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一乱投、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧顷编,春花似錦戚炫、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)施掏。三九已至,卻和暖如春茅糜,著一層夾襖步出監(jiān)牢的瞬間七芭,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工蔑赘, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狸驳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓缩赛,卻偏偏與公主長(zhǎng)得像耙箍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子酥馍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

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

  • 1辩昆,NSObject中description屬性的意義,它可以重寫嗎?答案:每當(dāng) NSLog(@"")函數(shù)中出現(xiàn) ...
    eightzg閱讀 4,143評(píng)論 2 19
  • 1. 父類實(shí)現(xiàn)深拷貝時(shí)物喷,子類如何實(shí)現(xiàn)深度拷貝卤材。父類沒(méi)有實(shí)現(xiàn)深拷貝時(shí),子類如何實(shí)現(xiàn)深度拷貝峦失。 1.1 深拷貝同淺拷貝...
    iYeso閱讀 1,891評(píng)論 0 13
  • 一、NSTimer的類方法和實(shí)例初始化方法 這三個(gè)方法直接將timer添加到了當(dāng)前runloop default ...
    打不死的小怪獸閱讀 9,881評(píng)論 0 6
  • 父類實(shí)現(xiàn)深拷貝時(shí)术吗,子類如何實(shí)現(xiàn)深度拷貝尉辑。父類沒(méi)有實(shí)現(xiàn)深拷貝時(shí),子類如何實(shí)現(xiàn)深度拷貝较屿。? 深拷貝同淺拷貝的區(qū)別:淺拷...
    JonesCxy閱讀 1,000評(píng)論 1 7
  • objc_getAssociatedObject返回與給定鍵的特定對(duì)象關(guān)聯(lián)的值隧魄。ID objc_getAssoci...
    有一種再見(jiàn)叫青春閱讀 1,580評(píng)論 0 7