Controller銷毀NSTimer釋放的細節(jié)

關(guān)于NSTimer釋放和內(nèi)存泄漏的問題。

@(NSTimer)[內(nèi)存管理,NSTimer釋放,循環(huán)引用]

首先需要再次明確最基礎(chǔ)的iOS引用計數(shù)內(nèi)存管理模式(按照說人話的方式):
1)自己生成的對象谒获,自己所持有恶复。
2)非自己生成的對象,自己也能持有。
3)自己持有的對象不再需要時被釋放。
4)非自己持有的對象無法釋放。


坑出現(xiàn)的地方

美工要求在簽到按鈕上顯示一個時間的label雏吭,于是就封裝了一個專門用于顯示時間TimeLabel;


簽到TimeLabel
@interface FMTimeLabel ()
@property (nonatomic, weak) NSTimer *timer;
@end

@implementation FMTimeLabel

- (instancetype)init {
    self = [super init];
    if (self) {
        //用NSTimer每秒循環(huán)執(zhí)行updateTime方法,達到更新label顯示內(nèi)容的目的煞额。
        _timer = [NSTimer scheduledTimerWithTimeInterval:1
                                                  target:self
                                                selector:@selector(updateTime)
                                                userInfo:nil
                                                 repeats:YES];
        [self updateTime];
    }
    return self;
}

- (void) updateTime {
    //每次執(zhí)行時思恐,獲取當(dāng)前時間的 時 分 秒 轉(zhuǎn)化成string類型給label顯示
    NSString *time = [FMUtils getTimeDescriptionByDate:[NSDate date] format:@"hh:mm:ss"];
    [self setText:time];
}

- (void)dealloc {
    if (_timer) {
        if ([_timer isValid]) {
            [_timer invalidate];
            _timer = nil;
        }
    }
}

@end

此時一切都很美好功能實現(xiàn)了效果也ok沾谜,但是多次刷新TableView之后,問題出現(xiàn)了胀莹。

timelabel直接卡死不再刷新時間基跑,并且也不走dealloc方法。

排查原因:內(nèi)存泄漏描焰,TimeLabel持有的NSTimer沒有被釋放媳否,導(dǎo)致TimeLabel也不能被釋放,從而導(dǎo)致線程掛起的狀態(tài)荆秦。

填坑的方法

問題分析:

原因是 Timer 添加到 Runloop 的時候篱竭,會被 Runloop 強引用,然后 Timer 又會有一個對 Target 的強引用(也就是 self )也就是說 NSTimer 強引用了 self 步绸,導(dǎo)致 self 一直不能被釋放掉掺逼,所以 self 的 dealloc 方法也一直未被執(zhí)行.

知道了錯誤原因,就先查一下NSTimer的官方文檔看看具體用法細節(jié)瓤介,發(fā)現(xiàn)NSTimer還有一個規(guī)則:(在哪個線程創(chuàng)建就要在哪個線程停止吕喘,否則會導(dǎo)致資源不能被正確的釋放。)那么問題來了:如果我就是想讓這個 NSTimer 一直輸出刑桑,直到 CustomerViewController 銷毀才停止并且釋放NSTimer氯质。

問題關(guān)鍵:

問題的關(guān)鍵就在于 self 被 NSTimer 強引用了,如果能打破這個強引用祠斧,問題應(yīng)該就能決了闻察。

問題解決:

(查閱到sunnyxxTEASON有寫到過相關(guān)問題的原理及解決方案)我們可以造一個假的 target 給 NSTimer 。這個假的 target 類似于一個中間的代理人琢锋,它做的唯一的工作就是挺身而出接下了 NSTimer 的強引用辕漂。(這個解決方案甚是巧妙)然后在self釋放的時候隨self一起釋放,然后層層解扣吩蔑,達到在ViewController銷毀的時候釋放NSTimer钮热,這個target類聲明如下:(摘自TEASON)

@interface HWWeakTimerTarget : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer* timer;
@end
@implementation HWWeakTimerTarget
 -(void) fire:(NSTimer *)timer {
    if(self.target) {
        [self.target performSelector:self.selector withObject:timer.userInfo];
    } else {
        [self.timer invalidate];
    }
}
@end

然后再封裝一個假的NSTimer的方法 scheduledTimerWithTimeInterval 方法填抬,但是在調(diào)用的時候已經(jīng)偷梁換柱了:

+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats {
    HWWeakTimerTarget* timerTarget = [[HWWeakTimerTarget alloc] init];
    timerTarget.target = aTarget;
    timerTarget.selector = aSelector;
    timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
                                                         target:timerTarget
                                                       selector:@selector(fire:)                                                                                                           
                                                       userInfo:userInfo
                                                        repeats:repeats];
    return timerTarget.timer;
}

至此將原來NSTimer換成封裝好的CustomerTimer再次運行烛芬,問題解決。

FMTimer.h

#import <Foundation/Foundation.h>

typedef void (^FMTimerHandler)(id userInfo);

@interface FMWeakTimer : NSObject

+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats;

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      block:(FMTimerHandler)block
                                   userInfo:(id)userInfo
                                    repeats:(BOOL)repeats;

@end

FMTimer.m

#import "FMWeakTimer.h"

@interface FMWeakTimerTarget : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer* timer;
@end

@implementation FMWeakTimerTarget
- (void) fire:(NSTimer *)timer {
    if(self.target) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self.target performSelector:self.selector withObject:timer.userInfo afterDelay:0.0f];
#pragma clang diagnostic pop
    } else {
        [self.timer invalidate];
    }
}
@end



@implementation FMWeakTimer

+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats{
    FMWeakTimerTarget* timerTarget = [[FMWeakTimerTarget alloc] init];
    timerTarget.target = aTarget;
    timerTarget.selector = aSelector;
    timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
                                                         target:timerTarget
                                                       selector:@selector(fire:)
                                                       userInfo:userInfo
                                                        repeats:repeats];
    return timerTarget.timer;
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      block:(FMTimerHandler)block
                                   userInfo:(id)userInfo
                                    repeats:(BOOL)repeats {
    NSMutableArray *userInfoArray = [NSMutableArray arrayWithObject:[block copy]];
    if (userInfo != nil) {
        [userInfoArray addObject:userInfo];
    }
    return [self scheduledTimerWithTimeInterval:interval
                                         target:self
                                       selector:@selector(_timerBlockInvoke:)
                                       userInfo:[userInfoArray copy]
                                        repeats:repeats];
    
}

+ (void)_timerBlockInvoke:(NSArray*)userInfo {
    FMTimerHandler block = userInfo[0];
    id info = nil;
    if (userInfo.count == 2) {
        info = userInfo[1];
    }
    if (block) {
        block(info);
    }
}

@end

                                                               ---玩的酷飒责,靠得住

僅做個人學(xué)習(xí)記錄所用赘娄,侵刪。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宏蛉,一起剝皮案震驚了整個濱河市遣臼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拾并,老刑警劉巖揍堰,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹏浅,死亡現(xiàn)場離奇詭異,居然都是意外死亡屏歹,警方通過查閱死者的電腦和手機隐砸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝙眶,“玉大人季希,你說我怎么就攤上這事∮姆祝” “怎么了式塌?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長友浸。 經(jīng)常有香客問我峰尝,道長,這世上最難降的妖魔是什么收恢? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任境析,我火速辦了婚禮,結(jié)果婚禮上派诬,老公的妹妹穿的比我還像新娘劳淆。我一直安慰自己,他們只是感情好默赂,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布沛鸵。 她就那樣靜靜地躺著,像睡著了一般缆八。 火紅的嫁衣襯著肌膚如雪曲掰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天奈辰,我揣著相機與錄音栏妖,去河邊找鬼。 笑死奖恰,一個胖子當(dāng)著我的面吹牛吊趾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瑟啃,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼论泛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蛹屿?” 一聲冷哼從身側(cè)響起屁奏,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎错负,沒想到半個月后坟瓢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勇边,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年折联,在試婚紗的時候發(fā)現(xiàn)自己被綠了粥诫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡崭庸,死狀恐怖怀浆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怕享,我是刑警寧澤执赡,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站函筋,受9級特大地震影響沙合,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜跌帐,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一首懈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谨敛,春花似錦究履、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至炊甲,卻和暖如春泥彤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卿啡。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工吟吝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人颈娜。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓剑逃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親揭鳞。 傳聞我的和親對象是個殘疾皇子炕贵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

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

  • 1.NSTimer的介紹 (1.)8種創(chuàng)建方法 <1> + (NSTimer *)timerWithTimeInt...
    liangZhen閱讀 7,449評論 0 6
  • 創(chuàng)建NSTimer 創(chuàng)建NSTimer的常用方法是: + (NSTimer *)scheduledTimerWit...
    LanWor閱讀 1,356評論 0 2
  • iOS中計時器常用的有兩種方式 使用NSTimer類(Swift 中更名為 Timer) NSTimer 常用的初...
    superDg閱讀 1,821評論 0 1
  • 燕兒穿過楊柳雨 終覓春風(fēng)十里 魚兒游戲芙蓉 嘻見葉落波起 清風(fēng)徐徐 池水卷了又舒 我笑甄玉斝 何必望桃花 因為有你
    MingHiker閱讀 183評論 0 0