NSTimer 進階使用總結與注意事項

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

1. NSTimer 容易泄漏

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

self.timer =[NSTimer scheduledTimerWithTimeInterval:1target:selfselector:@selector(update)userInfo:nilrepeats: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:1target:selfselector:@selector(update)userInfo:nilrepeats: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:1target:selfselector:@selector(update)userInfo:nilrepeats:YES];
以上代碼創建了一個 timer,但是沒有保存起來,后續自然也沒有機會停止這個 timer。所以會導致 timer 泄漏。

錯誤特征 3:
- (void)viewDidAppear:(BOOL)animated
{[super viewDidAppear:animated];self.timer =[NSTimer scheduledTimerWithTimeInterval:1target:selfselector:@selector(update)userInfo:nilrepeats: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

[NSObject performSelector:withObject:afterDelay:]
[NSObject performSelector:withObject:afterDelay:inMode:]
我們簡稱為 Perform Delay,他們的實現原理就是一個不循環(repeat 為 NO)的 timer。所以使用這兩個接口的注意事項跟使用 timer 類似。需要在適當的地方調用
[NSObject 
cancelPreviousPerformRequestsWithTarget: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。
比如這個: https://github.com/mindsnacks/MSWeakTimer。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/247936.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/247936.shtml
英文地址,請注明出處:http://en.pswp.cn/news/247936.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

一步一步教你實現iOS音頻頻譜動畫(一)

如果你想先看看最終效果再決定看不看文章 -> bilibili示例代碼下載 第二篇:一步一步教你實現iOS音頻頻譜動畫(二) 基于篇幅考慮,本次教程分為兩篇文章,本篇文章主要講述音頻播放和頻譜數據的獲取,下篇將…

微信小程序的基礎 (一)

微信小程序介紹- 鏈接 微信小程序,簡稱小程序,是一種不需要下載安裝即可使用的應用,它實現了應用“觸手可及”的夢想,用戶掃一掃或搜一下即可打開應用 1. 為什么是微信小程序? 微信有海量用戶,而且粘性很高&#x…

看YYModel源碼的一些收獲

關于源碼學習自己的一些感悟第一層:熟練使用;第二層:讀懂代碼;第三層:通曉原理;第四層:如何設計;自己學到了什么,還留有什么問題;關于分享關于線下演講分享和…

IDEA提交項目到SVN

一.提交步驟 VCS--Enable...-->點擊項目右鍵-->subversion-->share directory-->commit 二.IDEA SVN 忽略文件的設置 1》share .使用idea在將項目提交到svn的過程中遇到這樣的問題 將項目share之后再設置ignore files ,在commit的時候,不會將…

小程序基礎 (二)

小程序開發框架 小程序開發框架的目標是通過盡可能簡單、高效的方式讓開發者可以在微信中開發具有原生 APP 體驗的服務。 整個小程序框架系統分為兩部分:邏輯層(App Service)和 視圖層(View)。 小程序提供了自己的視…

項目ITP(五) spring4.0 整合 Quartz 實現任務調度

版權聲明:本文為博主原創文章,未經博主同意不得轉載。https://blog.csdn.net/u010378410/article/details/26016025 2014-05-16 22:51 by Jeff Li 前言 系列文章:[傳送門] 項目需求: 二維碼推送到一體機上,給學生簽到掃…

喜歡用Block的值得注意-Block的Retain Cycle的解決方法

本文不講block如何聲明及使用,只講block在使用過程中暫時遇到及帶來的隱性危險。 主要基于兩點進行演示: 1.block 的循環引用(retain cycle) 2.去除block產生的告警時,需注意問題。 有一次,朋友問我當一個對象中的block塊中的訪問…

小程序基礎 (三)

5. 使用 slot 使用單個slot // 頁面 <Test><view>自定義內容</view> </Test>// 組件 <view><view>前面的內容</view><slot></slot><view>后面的內容</view> </view>使用多個slot - 具名 // 頁面 &…

【PyQt5】QT designer + eclipse 集成開發

【寫在前面的話】 考慮將pyqt5的界面開發qt designer 集成在eclipse中&#xff0c;并且&#xff0c;不利用cmd命令行進行轉換。 【工具】 1、pyqt5 2、qt designer 3、eclipse pydy 【步驟】 1、首先配置Qt designer。 菜單 run-->external Tools-->External tools confi…

iOS UIlabel文字排版(改變字間距行間距)分類

在iOS開發中經常會用到UIlabel來展示一些文字性的內容&#xff0c;但是默認的文字排版會覺得有些擠&#xff0c;為了更美觀也更易于閱讀我們可以通過某些方法將UIlabel的行間距和字間距按照需要調節。 比如一個Label的默認間距效果是這樣&#xff1a; 然后用一個封裝起來的Cat…

MySQL查詢之聚合查詢

為了快速得到統計數據&#xff0c;提供了5個聚合函數&#xff1a; count(*)表示計算總行數&#xff0c;括號中寫星與列名&#xff0c;結果是相同的 查詢學生總數 select count(*) from students; max(列)表示求此列的最大值 查詢女生的編號最大值 select max(id) from students…

React基礎學習(第一天)

React 概述 : React 是一個用于 構建用戶界面 的 JavaScript 庫因為框架是有一整套解決方案的&#xff0c;React就是純粹寫UI組件的 沒有什么異步處理機制、模塊化、表單驗證這些。React和react-router, react-redux結合起來才叫框架&#xff0c;而React本身只是充當一個前端…

iOS 富文本風格NSMutableParagraphStyle、定制UITextView插入圖片和定制復制

問題一 開發過程中&#xff0c;經常會遇到動態計算行高的問題&#xff0c; - (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(nullableNSDictionary<NSString *, id> *)attributes context:(nullable NSStringDrawingC…

day24 01 初識繼承

day24 01 初識繼承 面向對象的三大特性&#xff1a;繼承&#xff0c;多態&#xff0c;封裝 一、繼承的概念 繼承&#xff1a;是一種創建新類的方式&#xff0c;新建的類可以繼承一個或者多個父類&#xff0c;父類又可稱基類或超類&#xff0c;新建的類稱為派生類或者子類 class…

React基礎學習(第二天)

虛擬DOM JSX 涉及到 虛擬DOM ,簡單聊一下 定時器渲染問題 // 方法 function render() {//2. 創建react對象let el (<div><h3>時間更新</h3><p>{ new Date().toLocaleTimeString()}</p></div>)//3. 渲染ReactDOM.render(el, document.g…

iOS 去除字符串中的空格或多余空格(適合英文單詞)

NSString -stringByTrimmingCharactersInSet: 是個你需要牢牢記住的方法。它經常會傳入 NSCharacterSet whitespaceCharacterSet 或 whitespaceAndNewlineCharacterSet 來刪除輸入字符串的頭尾的空白符號。 需要重點注意的是&#xff0c;這個方法 僅僅 去除了 開頭 和 結尾 的…

華為交換機在Telnet登錄下自動顯示接口信息

因為用console連接交換機&#xff0c;默認是自動顯示接口信息的&#xff0c;比如down掉一個接口后&#xff0c;會自動彈出接口被down掉的信息&#xff0c;但是在telnet連接下&#xff0c;默認是不顯示這些信息的&#xff0c;需要開啟后才可顯示。 1、首先開啟info-center(默認是…

React基礎學習(第三天)

條件渲染 1. if / else render () {if (this.state.isLoading) { // 正在加載中return <h1>Loading...</h1>}return <div>這就是我們想要的內容</div>} // 鉤子函數 五秒鐘之后 修改狀態值componentDidMount () { setTimeout(() > {this.setState(…

componentsJoinedByString 和 componentsSeparatedByString 的方法的區別

將string字符串轉換為array數組 NSArray *array [Str componentsSeparatedByString:","]; &#xff1d;&#xff1d;反向方法 將array數組轉換為string字符串 NSString *tempString [mutableArray componentsJoinedByString:","];--,是分隔符 可不加分隔…

java中的各種數據類型在內存中存儲的方式

轉載別人的附上鏈接&#xff1a;https://blog.csdn.net/zj15527620802/article/details/80622314 1.java是如何管理內存的 java的內存管理就是對象的分配和釋放問題。&#xff08;其中包括兩部分&#xff09; 分配&#xff1a;內存的分配是由程序完成的&#xff0c;程序員需要通…