iOS NSTimer 最佳實踐

achillescz 8年前發布 | 5K 次閱讀 NSTimer iOS開發 移動開發

NSTimer是iOS上的一種計時器,通過NSTimer對象,可以指定時間間隔,向一個對象發送消息。NSTimer是比較常用的工具,比如用來定時更新界面,定時發送請求等等。但是在使用過程中,有很多需要注意的地方,稍微不注意就會產生bug, crash, 內存泄漏。本文講解了使用NSTimer時需要注意的問題。

1. NSTimer 容易泄漏

比如以下代碼創建了一個計時器:

self.timer = 
  [NSTimer scheduledTimerWithTimeInterval:1
           target:self
           selector:@selector(update)
           userInfo:nil
           repeats:YES];

上述代碼,將創建一個無限循環的timer, 并投入當前線程的Runloop中開始執行。此時,Runloop會引用住timer, timer會引用住self, self則保存了timer. 如下圖所示:

需要注意的是,這種無限循環的timer, 會一直執行,需要調用 [timer invalidate] 顯式停止。否則runloop會一直引用著timer, timer又引用了self, 導致self整個對象泄漏,實際情況中,這個self有可能是一個view, 甚至是一個controller.

那, [timer invalidate] 要什么時候調用?有些人會在self的dealloc里面調用,這幾乎可以確定是錯誤的。因為timer會引用住self,在timer停止之前,是不會釋放self的,self的dealloc也不可能會被調用。

正確的做法應該是根據業務需要,在適當的地方啟動timer和停止timer. 比如timer是頁面用來更新頁面內部的view的,那可以選擇在頁面顯示的時候啟動timer,頁面不可見的時候停止timer. 比如:

- (void)viewWillAppear
{
  [super viewWillAppear];  self.timer =
    [NSTimer scheduledTimerWithTimeInterval:1
             target:self
             selector:@selector(update)
             userInfo:nil
             repeats:YES];
}

- (void)viewDidDisappear
{
  [super viewDidDisappear];
  [self.timer invalidate];
}

2. 錯誤特征

實際開發中,或者Code Review的時候,可以通過一些特征初步判定可能會有問題。

錯誤特征 1:

- (void)dealloc
{
  [self.timer invalidate];
}

以上代碼是有問題的。當timer沒有停止的時候,self會被引用,也就沒有機會走到dealloc. 同時,代碼作者應該對timer沒有正確的認識,所以需要review整個timer的使用情況。

錯誤特征 2:

[NSTimer scheduledTimerWithTimeInterval:1
         target:self
         selector:@selector(update)
         userInfo:nil
         repeats:YES];

以上代碼創建了一個timer,但是沒有保存起來,后續自然也沒有機會停止這個 timer. 所以會導致timer泄漏。

錯誤特征 3:

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];  self.timer =
    [NSTimer scheduledTimerWithTimeInterval:1
             target:self
             selector:@selector(update)
             userInfo:nil
             repeats:YES];
}

以上代碼也是有問題的。因為我們要確保timer的創建和銷毀必須是成對調用,否則會發生泄漏。而對于viewDidAppear其實很難找到一個準確的與之成對的方法(跟viewWillDisappear和viewDidDisappear都不是成對調用的),這里就需要檢查timer有沒有被重復創建和有沒有在適當的時機銷毀。

3. 停止 timer 可能會導致 self 對象銷毀

值得注意的是,調用 [timer invalidate] 停止timer,此時timer會釋放 target, 如果timer是最后一個持有target的對象,那么此次釋放會直接觸發target的 。比如:

- (void)onEnterBackground:(id)sender
{
    [self.timer invalidate];
    [self.view stopAnimation]; // dangerous!}

以上代碼,加入第一行的invalidate之后,self被銷毀了,那么第二行訪問self.view時候,就會觸發野指針crash。因為Objective-C的方法里面,self是沒有被retain的。這種情況,有個臨時的解決方案如下:

- (void)onEnterBackground:(id)sender
{
    __weak id weakSelf = self;
    [self.timer invalidate];
    [weakSelf.view stopAnimation]; // dangerous!}

將self改為弱引用。但是也是一個臨時解決方案。正確解決方法是,查出其它對象沒有引用self的時候,為什么timer還沒停止。這個案例告訴大家,當見到 invalidate被調用之后很神奇地出現了self野指針crash的時候,不要驚訝,就是timer沒處理好。

4. Perform Delay

<span style="font-size: 14px;">[NSObject performSelector:withObject:</span>

afterDelay:] 和 [NSObject performSelector

:withObject:afterDelay:inMode:]   我們簡稱

為Perform Delay, 他們的實現原理就是一個不循環(repeat 為 NO)的timer. 所以使用這兩個接口的注意事項跟使用timer類似。 所以使用這兩個接口的注意事項跟使用 timer 類似。需要在適當的地方調用  [NSObject cancelPreviousPerform

RequestsWithTarget:selector:object:]

5. Runloop Mode

注意創建NSTimer或者調用Perform Delay方法,都是往當前線程的Runloop 中投遞消息,大部分接口的默認投遞模式是CFRunloopDefaultMode. 也就是說,Runloop不在DefaultMode下運行的時候(比如滾動列表的時候主線程的runloop mode是CFRunloopTrackingMode),消息將被暫時阻塞,不能及時處理。

6. Weak Timer

NSTimer之所以比較難用對,比較重要的原因主要是NSTimer對target是強引用的。這導致了target泄漏,或者生命周期超出開發者的預期。timer如果對target是弱引用的話,這些問題就不存在了,這就是Weak Timer.

Weak Timer的實現方式分為兩種,第一種是在NSTimer和target中間加多一層代理(Proxy),代理作為target被NSTimer強引用,同時弱引用真正的target,并對它轉發消息。示例圖如下:

+ (NSTimer *)qz_scheduledWeakTimerWithTimeInterval:
(NSTimeInterval)ti target:(id)target selector:
(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats
{
    QzoneWeakProxy *proxy = [[QzoneWeakProxy weakProxyForObject:target]; 
    return [self scheduledTimerWithTimeInterval:
    ti target:proxy selector:aSelector userInfo:userInfo repeats:repeats];
}

第二種方案是用dispatch timer自己實現一遍timer, 具體實現里面,弱引用 target.

 

 

來自:http://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=2649796863&idx=1&sn=b3958012bfddfbb607c6ac38024ad126&chksm=f1fcc4a7c68b4db1f73c891eda199f174fa5978094387514c1d21387ff33484c6489e71075c7&scene=0

 

 本文由用戶 achillescz 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!