iOS開發NSTimer的深入理解
再一次面試中被問到nstimer的爭取使用方法,原理,我當時就說了[_timer invalidate],timer =nil;
但是原理沒說明,後來經過查詢資料查到了,引用原著作者http://www.jianshu.com/p/330d7310339d
發現ViewController的dealloc方法死活沒走到, 心裡咯噔一下, 不會又記憶體洩漏了? ?
一切都是很完美的節奏啊: ViewController初始化時, 建立Sub UIView, 建立資料結構, 建立NSTimer
然後在dealloc裡, 釋放NSTimer, 然後NSTimer = nil, 哪裡會有什麼問題?
不對! 移除NSTimer後dealloc就愉快滴走了起來, 難道NSTimer的用法一直都不對?
結果發現, 真的是不對! ?
好吧, 故事就講到這裡, 馬上開始今天的NSTimer之旅吧
建立NSTimer
建立NSTimer的常用方法是
+ (NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)titarget:(id)targetselector:(SEL)aSelectoruserInfo:(id)userInforepeats:(BOOL)repeats
建立NSTimer的不常用方法是
+ (NSTimer*)timerWithTimeInterval:(NSTimeInterval)titarget:(id)targetselector:(SEL)aSelectoruserInfo:(id)userInforepeats:(BOOL)repeats
和
- (instancetype)initWithFireDate:(NSDate*)date interval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
這幾種方法除了建立方式不同(引數), 方法型別不同(類方法, 物件方法), 還有其他不同麼?
當然有, 不然Apple沒必要這麼作, 開這麼多介面, 作者(好像就是我?)也沒必要這麼作, 寫這麼長軟文
他們的區別很簡單:
how-to-user-nstimer-01.png
scheduledTimerWithTimeInterval相比它的小夥伴們不僅僅是建立了NSTimer物件, 還把該物件加入到了當前的runloop中!
等等, runloop是什麼鬼? 在此不解釋runloop的原理, 但是使用NSTimer你必須要知道的是
NSTimer只有被加入到runloop, 才會生效, 即NSTimer才會被真正執行
所以說, 如果你想使用timerWithTimeInterval或initWithFireDate的話, 需要使用NSRunloop的以下方法將NSTimer加入到runloop中
-(void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
how-to-user-nstimer-02.png
銷燬NSTimer
知道了如何建立NSTimer後, 我們來說說如何銷燬NSTimer, 銷燬NSTimer不是很簡單的麼?
用invalidate方法啊, 好像還有個fire方法, 實在不行直接將NSTimer物件置nil, 這樣iOS系統就幫我們銷燬了
是的, 曾經的我也是如此混沌滴這麼認為著, 那麼這幾種方法是不是真的都可以銷燬NSTimer呢?
invalidate與fire
我們來看看Apple Documentation對這兩個方法的權威解釋吧
invalidate
Stops the receiver from ever firing again and requests its removal from its run loop
This method is the only way to remove a timer from an NSRunLoop object
fire
Causes the receiver’s message to be sent to its target
If the timer is non-repeating, it is automatically invalidated after firing
理解了上面的幾句話, 你就完完全全理解了invalidate和fire的區別了, 下面的示意圖更直觀
how-to-user-nstimer-03.png
總之, 如果想要銷燬NSTimer, 那麼確定, 一定以及肯定要呼叫invalidate方法
invalidate與=nil
就像銷燬其他強應用(不用我解釋強引用了吧, 否則你還是別浪費時間往下看了)物件一樣, 我們是否可以將NSTimer置nil, 而讓iOS系統幫我們銷燬NSTimer呢?
答案是: 當然不可以! (詳見上述的結論, "總之, 巴拉巴拉...")
為什麼不可以? 其他強引用物件都可以, 為什麼NSTimer物件不可以? 你說不可以就可以? 憑什麼信你?
好吧, 我們來看下使用NSTimer時, ARC是怎麼工作的
首先, 是建立NSTimer, 加入到runloop後, 除了ViewController之外iOS系統也會強引用NSTimer物件
how-to-user-nstimer-04.png
當呼叫invalidate方法時, 移除runloop後, iOS系統會解除對NSTimer物件的強引用, 當ViewController銷燬時, ViewController和NSTimer就都可以釋放了
how-to-user-nstimer-05.png
當將NSTimer物件置nil時, 雖然解除了ViewController對NSTimer的強引用, 但是iOS系統仍然對NSTimer和ViewController存在著強引用關係
神馬? iOS系統對NSTimer有強引用很好理解, 對ViewController本來不就是強引用麼?
這裡所說的iOS系統對ViewController的強引用, 不是指為了實現View顯示的強引用, 而是指iOS為了實現NSTimer而對ViewController進行的額外強引用 (我去, 能不能不要這麼拗口, 欺負我語文不好)
不瞞您說, 我的語文其實也是一般般, 所以show me the code
NSLog(@"Retain count is %ld",CFGetRetainCount((__bridgeCFTypeRef)self));_timer = [NSTimerscheduledTimerWithTimeInterval:TimerInterval target:selfselector:@selector(timerSelector:) userInfo:nilrepeats:TimerRepeats];NSLog(@"Retain count is %ld",CFGetRetainCount((__bridgeCFTypeRef)self));[_timer invalidate];NSLog(@"Retain count is %ld",CFGetRetainCount((__bridgeCFTypeRef)self));
各位請注意, 建立NSTimer和銷燬NSTimer後, ViewController(就是這裡的self)引用計數的變化
2016-07-06 13:53:21.950 NSTimerAndDeallocDemo[2028:697020] Retain count is 7
2016-07-06 13:53:21.950 NSTimerAndDeallocDemo[2028:697020] Retain count is 8
2016-07-06 13:53:21.950 NSTimerAndDeallocDemo[2028:697020] Retain count is 7
如果你還是不理解, 那隻能用"殺手鐗"了, 美圖伺候!
how-to-user-nstimer-06.png
關於上圖, @JasonHan0991 有不同的解釋, 詳見評論區, 在此表示感謝!
結論
綜上所述, 銷燬NSTimer的正確姿勢應該是
[_timerinvalidate];// 真正銷燬NSTimer物件的地方_timer=nil;// 物件置nil是一種規範和習慣
慢著, 這個結論好像不妥吧?
這都被你發現了! 銷燬NSTimer的時機也是至關重要的!
如果將上述銷燬NSTimer的程式碼放到ViewController的dealloc方法裡, 你會發現dealloc還是永遠不會走的
所以我們要將上述程式碼放到ViewController的其他生命週期方法裡, 例如ViewWillDisappear中
綜上所述, 銷燬NSTimer的正確姿勢應該是 (這句話我怎麼看著這麼眼熟, 是的, 這次真的結論了)
- (void)viewWillDisappear:(BOOL)animated { [superviewWillDisappear:animated]; [_timer invalidate]; _timer = nil;}
NSTimer與runloop
上面說到scheduledTimerWithTimeInterval方法時, 有這麼一句
schedules it on the current run loop in the default mode
加到runloop這件事就不必再解釋了, 而這個default mode應該如何理解呢?
其實我是不想談runloop的(因為理解不深, 所以怕誤導人民群眾), 但是這裡不得不解釋下了
runloop會執行在不同的mode, 簡單來說有以下兩種mode
NSDefaultRunLoopMode, 預設的mode
UITrackingRunLoopMode, 當處理UI滾動操作時的mode
所以scheduledTimerWithTimeInterval建立的NSTimer在UI滾動時, 是不會被及時觸發的, 因為此時NSTimer被加到了default mode
如果想要runloop執行在UITrackingRunLoopMode時, 仍然及時觸發NSTimer那應該怎麼辦呢?
應該使用timerWithTimeInterval或initWithFireDate, 在建立完NSTimer後, 自己加入到指定的runloop mode
[[NSRunLoop currentRunLoop]addTimer:_timerforMode:NSRunLoopCommonModes];
NSRunLoopCommonModes又是什麼鬼? 不是說好的只有兩種mode麼?
是滴, 請注意這裡的複數形式modes, 說明它不是一個mode, 它是mode的集合!
通常情況下NSDefaultRunLoopMode和UITrackingRunLoopMode都已經被加入到了common modes集合中, 所以不論runloop執行在哪種mode下, NSTimer都會被及時觸發
最後, 我們來做個小測驗, 來結束今天的NSTimer討論吧
測驗: 請問下面的NSTimer哪個更準時?
// 1[NSTimerscheduledTimerWithTimeInterval:TimerInterval target:selfselector:@selector(timerSelector:) userInfo:nilrepeats:TimerRepeats];
// 2[[NSRunLoopcurrentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
// 3[[NSRunLoopcurrentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
答案, 第三種是最準確的,不受其他情況影響,第一種和第二種情況相同都是加入到NSDefaultRunLoopMode模式下,如果滑動scrollview的時候,時間計時器會停止,等滑動結束,再繼續,所以時間不準確
最後,如果本文章對你有幫助,希望點贊,評論666
本人github開源庫地址:https://github.com/lishiping,希望對廣大iOS開發者有幫助
Cocoapod公開庫:
1.SafeData---使用array,dictionary類別,主要是保護陣列防止插入空陣列,防止陣列越界,在開發中非常實用,字典的類別也非常實用,可以直接得到轉換後的型別SafeData
2.SPDebugBar ---A tool to help developers and testers quickly switch the server address, convenient to debug the program.一個小工具幫助開發人員和測試人員快速切換伺服器地址,方便除錯程式,可以在debug模式下或者測試包上方便切換地址SPDebugBar
3.SPFastPush---Use Macro Fast NavigationController push next VC,and Fast Pop anther VC.使用巨集幫助導航控制器快速push下一個Viewcontroller,並使用KVC快速給下一個VC賦值,也可以使用巨集定義快速返回上一層VC或者指定的VCSPFastPush
4.SPMacro---一些foundation層的一些巨集定義,列印巨集定義,型別判斷,通知使用,執行緒使用等,和一些UIKit層的巨集定義,螢幕方面的巨集定義,顏色方面的巨集定義,圖片巨集定義SPMacro
5.SPWebView---SPWebView是一個基於OC程式碼實現的WebView輕量級元件,將UIWebView和WKWebView的API封裝成統一的類去使用,並且在載入網頁的時候提供進度條,同時簡化JS與OC互相呼叫及傳遞資料的方式。仿微信介面SPWebView
6.SPCategory---SPWebView是一個常用類別庫,size,字串,hud等SPCategory
相關文章
- iOS開發中深入理解CADisplayLink和NSTimeriOS
- 深入理解 iOS 開發中的鎖iOS
- iOS:iOS8開發 深入理解autolayout(3)iOS
- iOS開發筆記(七):深入理解 AutoreleaseiOS筆記
- iOS開發框架MVVM 1理解開發模式iOS框架MVVM模式
- iOS 關於NSTimer的迴圈引用iOS
- 移動前端開發之viewport的深入理解前端View
- iOS 中 NSTimer 使用詳解iOS
- iOS開發Runtime的理解與應用iOS
- 深入理解 iOS Rendering ProcessiOS
- iOS如何安全而又優雅的使用NSTimeriOS
- 深入理解 iOS App 的啟動過程iOSAPP
- iOS刨根問底-深入理解GCDiOSGC
- iOS學習之深入理解RunLoopiOSOOP
- iOS多執行緒的初步研究(四)-- NSTimeriOS執行緒
- 深入理解rtmp(一)之開發環境搭建開發環境
- 深入理解jQuery外掛開發總結(三)jQuery
- 深入理解jQuery外掛開發總結(一)jQuery
- iOS開發基礎146-深入解析WKWebViewiOSWebView
- iOS專案開發實戰——理解frame,bounds,centeriOS
- iOS開發之 Method Swizzling 深入淺出iOS
- 玩轉iOS開發:iOS中的GCD開發(一)iOSGC
- 玩轉iOS開發:iOS中的GCD開發(三)iOSGC
- 玩轉iOS開發:iOS中的GCD開發(二)iOSGC
- iOS 深入理解KVO實現 | 掘金技術徵文iOS
- 深入理解 Swift 派發機制Swift
- 深入理解併發和並行並行
- Flutter完整開發實戰詳解(十一、全面深入理解Stream)Flutter
- 深入理解Nginx:模組開發與架構解析(第2版)Nginx架構
- 玩轉iOS開發:iOS中的NSOperation開發(一)iOS
- 玩轉iOS開發:iOS中的NSOperation開發(二)iOS
- 深入淺出this的理解
- iOS學習之深入理解程式編譯過程iOS編譯
- iOS開發中GCD在多執行緒方面的理解iOSGC執行緒
- 深入理解Java多執行緒與併發框(第⑧篇)——深入理解:CASJava執行緒
- iOS開發系列--IOS程式開發概覽iOS
- 我所理解的 iOS 併發程式設計iOS程式設計
- iOS 開發iOS