NSTimer
- 作用:文檔又講:在固定的時間間隔被觸發(fā),給指定目標(biāo)發(fā)送消息。
- NSTimer使用必須注意點?
- 要想timer能夠運行起來,必須將timer實例 添加到 指定線程的Runloop下某個model下. 上面一句話蘊含幾點:
1.1. 必須存在線程
1.2. 指定線程的Runloop必須啟動
1.3. 必須將timer添加到Runloop的某個model下(即:timer作為Model的item)
- 要想timer能夠運行起來,必須將timer實例 添加到 指定線程的Runloop下某個model下. 上面一句話蘊含幾點:
- Runloop 與 timer 關(guān)系? 文檔中是這樣寫的:
Once scheduled on a run loop, the timer fires at the specified interval until it is invalidated. A nonrepeating timer invalidates itself immediately after it fires. However, for a repeating timer, you must invalidate the timer object yourself by calling its
invalidatemethod. Calling this method requests the removal of the timer from the current run loop; as a result, you should always call theinvalidatemethod from the same thread on which the timer was installed. Invalidating the timer immediately disables it so that it no longer affects the run loop. The run loop then removes the timer (and the strong reference it had to the timer), either just before theinvalidatemethod returns or at some later point. Once invalidated, timer objects cannot be reused.
上面文檔提出幾個注意點:
- timer必須運行在runloop中,不重復(fù)的timer,觸發(fā)后系統(tǒng)立即使其自身無效. 重復(fù)的timer,必須調(diào)用invalidate方法才能使timer無效,且調(diào)用invalidate方法后會請求從runloop刪除timer.
- 使用timer與廢棄timer必須在同一個線程中.
- Runloop會在invalidate方法返回之前或之后的某個時間點移除timer(即:移除runloop對timer的強引用)
- 一旦失效,就不能重用計時器對象。
- 根據(jù)2中文檔所知,runloop會對timer有強引用.
- timer會對目標(biāo)對象target進行強引用
5.invalidate方法作用? 文檔說明:
Stops the timer from ever firing again and requests its removal from its run loop.
//即:停止timer再次被觸發(fā),且請求從runloop中移除
NSTimer如何解決循環(huán)引用?
-
首選講NSTimer使用時產(chǎn)生循環(huán)引用的原因?
// 實線:強引用,虛線:弱引用
屏幕快照 2019-09-17 下午6.10.05.png
- 可以看出無論self對timer是強/弱引用,timer始終強持有self實例.當(dāng)self強引用timer時,self與timer相互引用,肯定造成循環(huán).當(dāng)self弱引用timer時,timer一旦被觸發(fā),則timer一定被添加到runloop中,這時runloop強持有timer,timer強持有self,若self是NSObject,UIView,UIViewController的實例,就要求runloop退出(model的item為空)或被釋放(線程被銷毀),但runloop對應(yīng)線程是主線程時,就造成self無法被釋放.
- 解決方案?
- 直接方法:
2.1.1. 使用iOS10之后新出的block回調(diào)方式.
2.1.2.攔截pop方法,廢棄timer.不同開發(fā)人有不同的方案.我這里是通過自定義navigation,重寫pop方法,下沉給nav.toViewController, self實現(xiàn)相應(yīng)方法,調(diào)用timer的invalidate
2.1.3. 在viewDidDisappear里 調(diào)用timer的invalidate.(self is UIViewController) - 類方法方案:(強力推薦,因為無需外部調(diào)用timer的invalidate,避免遺忘)
就是通過將timer的target設(shè)置為一個類對象.雖然相互持有關(guān)系沒有被打破,可是因為類對象(class object)無需回收,所以不用擔(dān)心。
- 消息轉(zhuǎn)發(fā)方案:
就是通過將timer的target設(shè)置為一個NSProxy的實例,自定義繼承NSProxy類,實現(xiàn)消息轉(zhuǎn)發(fā)方法. 就是說:timer調(diào)用target(proxy實例)的selector,利用消息轉(zhuǎn)發(fā)原理,回調(diào)self的selector
- RunTime方案:
就是隨意創(chuàng)建一個object作為timer的selector,給object添加實例方法,添加的實例方法就是timer觸發(fā)的消息方法.
- Block方案:
即給NSTimer添加分類,添加類方法獲取timer實例,將timer的target設(shè)置為NSTimer類對象(簡介利用了類方法方案),打破timer對self的強持有關(guān)系.
具體實現(xiàn)代碼https://github.com/zhbgitHub/NSTimerUse
代碼中注釋有應(yīng)該的注意點及timer占用資源的釋放說明.
- 使用GCD定時器取代NSTimer(較簡單直接貼代碼)
@interface xxx ()
@property (nonatomic, strong) dispatch_source_t timer;
@end
- (void)startTimer
{
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue);
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), 2.0*NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"執(zhí)行了");
});
});
//開啟計時器
dispatch_resume(_timer);
}
- (void)stopTimer
{
dispatch_source_cancel(self.timer);
}
