Crash 防護方案(四):NSTimer

原文 : 與佳期的個人博客(gonghonglou.com)

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

用此方法創(chuàng)建出來的計時器,會在指定的時間間隔之后執(zhí)行任務。也可以令其反復執(zhí)行任務,直到開發(fā)者稍后將其手動關閉為止牵祟。target 與 selector 參數表示計時器將在哪個對象上調用哪個方法。計時器會保留其目標對象鄙陡,等到自身“失效”時再釋放此對象。調用 invalidate 方法可令計時器失效霹俺;執(zhí)行完相關任務之后柔吼,一次性的計時器也會失效。開發(fā)者若將計時器設置成重復執(zhí)行模式丙唧,那么必須自己調用 invalidate 方法,才能令其停止觅玻。

由于計時器會保留其目標對象想际,所以反復執(zhí)行任務通常會導致應用程序出問題培漏。也就是說,設置成重復執(zhí)行模式的那種計時器胡本,很容易引入“保留環(huán)”牌柄。

這是《Effective Objective-C 2.0》書中”第 52 條:別忘了 NSTimer 會保留其目標對象“ 一章中的說法。蘋果在其文檔中的說明:

repeats
If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.

并且我們在 Demo 中實驗也確實如此侧甫,調用 + (NSTimer *)scheduledTimerWithTimeInterval: 方法時如果 repeats = NO 的話是沒什么問題的珊佣,執(zhí)行一次后 NSTimer 會自動 invalidate,但 repeats = YES 的話并不會披粟,而且因為 NSTimer 的寫法是這樣的:

- (void)dealloc {
    [_timer invalidate];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeatLog) userInfo:nil repeats:YES];
}

- (void)repeatLog {
    NSLog(@"timer");
}

self 持有 timer咒锻,timer 設置了 target 又會持有 self,造成循環(huán)引用守屉,所以 dealloc 永遠不會執(zhí)行惑艇。反復執(zhí)行任務則有可能出現崩潰。當然蘋果在 iOS10 之后出了新的方法使用 block 的方式可以避免循環(huán)引用:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                    repeats:(BOOL)repeats
                                      block:(void (^)(NSTimer *timer))block
API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

但我們總要兼容老版本拇泛,很少有 APP 會直接舍棄 iOS 10 之前的用戶滨巴。所以《Effective Objective-C 2.0》書中也給出的解決方案是給 NSTimer 添加一個 Category,在 Category 里添加對 +scheduledTimerWithTimeInterval: 方法的封裝方法俺叭,也就是想達到這樣的效果:在 VC 中使用的時候恭取,self 持有 timer,timer 持有 category(NSTimer 類對象)熄守,self 調用 timer 的時候傳入 block 給 category 執(zhí)行蜈垮。這樣就能避免循環(huán)引用了。

BlocksKit 實現

BlocksKit 也提供了一個 NSTimer+BlocksKit.m 分類實現了相同的功能

BlocksKit 最新的 tag(2.2.5)及之前的版本的實現是:

@implementation NSTimer (BlocksKit)

+ (id)bk_scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)(NSTimer *timer))block repeats:(BOOL)inRepeats
{
    NSParameterAssert(block != nil);
    return [self scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(bk_executeBlockFromTimer:) userInfo:[block copy] repeats:inRepeats];
}

+ (id)bk_timerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)(NSTimer *timer))block repeats:(BOOL)inRepeats
{
    NSParameterAssert(block != nil);
    return [self timerWithTimeInterval:inTimeInterval target:self selector:@selector(bk_executeBlockFromTimer:) userInfo:[block copy] repeats:inRepeats];
}

+ (void)bk_executeBlockFromTimer:(NSTimer *)aTimer {
    void (^block)(NSTimer *) = [aTimer userInfo];
    if (block) block(aTimer);
}

@end

代碼很簡單柠横,正如《Effective Objective-C 2.0》書中所講的思路:

這段代碼將計時器所應執(zhí)行的任務封裝成“塊”窃款,在調用的計時器函數時,把它作為 userInfo 參數傳進去牍氛。該參數可用來存放“萬能值”晨继,只要計時器還有效,就會一直保留著它搬俊。傳入參數時要通過 copy 方法將 block 拷貝到“堆”上紊扬,否則等到稍后要執(zhí)行他的時候,該塊可能已經無效了唉擂。計時器現在的 target 是 NSTimer 類對象餐屎,這是個單例,因為計時器是否會保留它玩祟,其實都無所謂腹缩。此處依然有保留環(huán),然而因為類對象(class object)無須回收,所以不用擔心藏鹊。

只需要在使用的時候注意避免 block 產生循環(huán)引用即可润讥,用 __weak typeof(self) weakSelf = self;__strong typeof(weakSelf) strongSelf = weakSelf; 即可避免盘寡。

BlocksKit 新實現(未打 tag)

值得提一下的是 BlocksKit 當前的最新代碼里的 NSTimer+BlocksKit.m 又有了不同的實現楚殿,直接拋棄了 NSTimer,而是用了 CFRunLoopTimerCreateWithHandler 來實現:

@implementation NSTimer (BlocksKit)

+ (instancetype)bk_scheduleTimerWithTimeInterval:(NSTimeInterval)seconds repeats:(BOOL)repeats usingBlock:(void (^)(NSTimer *timer))block
{
    NSTimer *timer = [self bk_timerWithTimeInterval:seconds repeats:repeats usingBlock:block];
    [NSRunLoop.currentRunLoop addTimer:timer forMode:NSDefaultRunLoopMode];
    return timer;
}

+ (instancetype)bk_timerWithTimeInterval:(NSTimeInterval)inSeconds repeats:(BOOL)repeats usingBlock:(void (^)(NSTimer *timer))block
{
    NSParameterAssert(block != nil);
    CFAbsoluteTime seconds = fmax(inSeconds, 0.0001);
    CFAbsoluteTime interval = repeats ? seconds : 0;
    CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent() + seconds;
    return (__bridge_transfer NSTimer *)CFRunLoopTimerCreateWithHandler(NULL, fireDate, interval, 0, 0, (void(^)(CFRunLoopTimerRef))block);
}

@end

Demo 地址:GHLCrashGuard

后記

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末变隔,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子常潮,更是在濱河造成了極大的恐慌弟胀,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喊式,死亡現場離奇詭異孵户,居然都是意外死亡,警方通過查閱死者的電腦和手機岔留,發(fā)現死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門夏哭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人献联,你說我怎么就攤上這事竖配。” “怎么了里逆?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵进胯,是天一觀的道長。 經常有香客問我原押,道長胁镐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任诸衔,我火速辦了婚禮盯漂,結果婚禮上,老公的妹妹穿的比我還像新娘笨农。我一直安慰自己就缆,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布谒亦。 她就那樣靜靜地躺著竭宰,像睡著了一般空郊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上羞延,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天渣淳,我揣著相機與錄音脾还,去河邊找鬼伴箩。 笑死,一個胖子當著我的面吹牛鄙漏,可吹牛的內容都是我干的嗤谚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼怔蚌,長吁一口氣:“原來是場噩夢啊……” “哼巩步!你這毒婦竟也來了?” 一聲冷哼從身側響起桦踊,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤椅野,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后籍胯,有當地人在樹林里發(fā)現了一具尸體竟闪,經...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年杖狼,在試婚紗的時候發(fā)現自己被綠了炼蛤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蝶涩,死狀恐怖理朋,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情绿聘,我是刑警寧澤嗽上,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站熄攘,受9級特大地震影響兽愤,放射性物質發(fā)生泄漏。R本人自食惡果不足惜鲜屏,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一烹看、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧洛史,春花似錦惯殊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽务热。三九已至,卻和暖如春己儒,著一層夾襖步出監(jiān)牢的瞬間崎岂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工闪湾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留冲甘,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓途样,卻偏偏與公主長得像江醇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子何暇,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,096評論 1 32
  • 定時器的用法 系統(tǒng)提供了8個創(chuàng)建方法陶夜,6個類創(chuàng)建方法,2個實例初始化方法裆站。有三個方法直接將timer添加到...
    gpylove閱讀 1,814評論 1 3
  • 1.設計模式是什么条辟? 你知道哪些設計模式,并簡要敘述宏胯? 設計模式是一種編碼經驗羽嫡,就是用比較成熟的邏輯去處理某一種類...
    司馬DE晴空閱讀 1,290評論 0 7
  • 設計模式是什么? 你知道哪些設計模式胳嘲,并簡要敘述厂僧? 設計模式是一種編碼經驗,就是用比較成熟的邏輯去處理某一種類型的...
    iOS菜鳥大大閱讀 707評論 0 1
  • 培養(yǎng)一個有教養(yǎng)了牛、三觀正的孩子颜屠,TA的人生道路會更加明朗,因為他們會吸納更多欣賞的眼光鹰祸,得到更多的有利幫助甫窟。
    雨芯微創(chuàng)業(yè)閱讀 113評論 0 0