iOS開發(fā) 計(jì)時(shí)器的實(shí)現(xiàn)方式

iOS中計(jì)時(shí)器常用的有兩種方式

使用NSTimer類(Swift 中更名為 Timer)

NSTimer 常用的初始化方法區(qū)別

方法一

objc
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
swift
class func scheduledTimer(timeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats yesOrNo: Bool) -> Timer

  • 創(chuàng)建一個(gè)創(chuàng)建一個(gè)NSTimer對(duì)象,并添加到當(dāng)前的RunLoop中,使用默認(rèn)模式RunLoop.current.add(timer, forMode: .defaultRunLoopMode),不需要手動(dòng)fire,系統(tǒng)會(huì)自動(dòng)執(zhí)行綁定的aSelector
  • 如果設(shè)置了repeatstrue,每隔一定的時(shí)間會(huì)重復(fù)執(zhí)行aSelector,直到手動(dòng)調(diào)用invalidate方法。repeatsfalse,執(zhí)行一次之后系統(tǒng)會(huì)自動(dòng)調(diào)用invalidate
方法二

objc
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
swift
init(timeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats yesOrNo: Bool)

  • 創(chuàng)建一個(gè)創(chuàng)建一個(gè)NSTimer對(duì)象,需要手動(dòng)添加到RunLoop中,手動(dòng)添加到RunLoop中之后開始計(jì)時(shí),執(zhí)行aSelector
  • 如果設(shè)置了repeatstrue,每隔一定的時(shí)間會(huì)重復(fù)執(zhí)行aSelector,直到手動(dòng)調(diào)用invalidate方法。repeatsfalse,執(zhí)行一次之后系統(tǒng)會(huì)自動(dòng)調(diào)用invalidate
方法三 需要iOS 10以上

objc
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
swift
init(timeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void)

  • 這個(gè)方法和方法二一樣,使用block回調(diào)。block中自帶了NSTimer參數(shù),避免循環(huán)引用
方法四 需要iOS 10以上

objc
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
swift
class func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer

  • 這個(gè)方法和方法一一樣,使用block回調(diào)。block中自帶了NSTimer參數(shù),避免循環(huán)引用
注釋
  • timer 對(duì)象中都是強(qiáng)引用。targetuserInfo,添加到RunLoop中也是強(qiáng)引用timer。
  • timer 對(duì)象調(diào)用invalidate方法后,會(huì)從RunLoop中移除引用,timer不會(huì)執(zhí)行對(duì)應(yīng)的方法或者block,timer會(huì)移除RunLoop的引用和userInfo、target的引用,也是為一個(gè)方法可以移除timer引用的方法。
  • 官方特別注釋
  • You must send this message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.大致意思就是計(jì)時(shí)器在那個(gè)線程上創(chuàng)建開始的,就需要在那個(gè)線程上調(diào)用此方法,不過一般我都是在主線程上創(chuàng)建timer,所以在這也沒遇到什么坑
  • fire方法只適合觸發(fā)重復(fù)的計(jì)時(shí)器,并且不會(huì)重置計(jì)時(shí)器的計(jì)時(shí)時(shí)間。如果不是重復(fù)的計(jì)時(shí)器,觸發(fā)之后會(huì)自動(dòng)失效,不管計(jì)時(shí)器時(shí)間有沒有到
  • 官方注釋如下:
  • You can use this method to fire a repeating timer without interrupting its regular firing schedule. If the timer is non-repeating, it is automatically invalidated after firing, even if its scheduled fire date has not arrived.

Timer 內(nèi)存管理

Timer對(duì)參數(shù)的引用都是強(qiáng)引用,而且添加到RunLoop中也是強(qiáng)引用。在ViewController中,controller持有timerRunLoop持有timer,一般在dealloc中調(diào)用timer.invalidate,但是此時(shí)RunLoop仍然持有timer,并不會(huì)走dealloc,導(dǎo)致ViewController無法釋放。要求不要的可以在viewWillDisappear:中調(diào)用timer.invalidate。這也只能在當(dāng)前頁面使用定時(shí)器,離開了這個(gè)頁面就不行了。網(wǎng)上有折中的方法就是ViewController不持有timer,借助單例類來實(shí)現(xiàn),這只能算折中的方法。在下方補(bǔ)充中有一種解決方案。

使用GCD

GCD在swift3以后改動(dòng)很大,所以分開來說

objc

uint64_t interval = 5 * NSEC_PER_SEC;
//創(chuàng)建一個(gè)專門執(zhí)行timer回調(diào)的GCD隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("timerQueue", 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//使用dispatch_source_set_timer函數(shù)設(shè)置timer參數(shù)
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
//設(shè)置回調(diào)

__weak __typeof(self) ws = self
dispatch_source_set_event_handler(_timer, ^(){ [ws sendRequest]; });
dispatch_resume(_timer);
dealloc方法
- (void)dealloc
{
if (_timer != NULL) {
dispatch_cancel(_timer);
_timer = NULL;
}
}

  • 使用dispatch_source_create方法生成dispatch_source_t實(shí)例,并且設(shè)置回調(diào)。
  • dispatch_resume(_timer);執(zhí)行這個(gè)方法timer才會(huì)開始,默認(rèn)是suspend暫停、掛起狀態(tài)
  • dispatch_suspend(_timer);暫停timer,計(jì)時(shí)器出于暫停狀態(tài),重新啟用調(diào)用dispatch_resume(_timer);

swift

var ti : DispatchSourceTimer!
ti = DispatchSource.makeTimerSource(flags:DispatchSource.TimerFlags.init(rawValue: 0) , queue: nil);
ti.schedule(deadline: DispatchTime.now(), repeating: 2.0)
ti.setEventHandler { [weak self] in self?.timerAction() }
ti.resume()
deinit { if ti != nil { ti.suspend() } }

  • ti.resume()開始計(jì)數(shù)器,否則不是主動(dòng)開始
  • ti.suspend()暫停、掛起計(jì)時(shí)器,重新開始ti.resume()

總結(jié)

如果在一個(gè)頁面里面需要使用定時(shí)器,離開頁面就停止,個(gè)人傾向于使用NSTimer類實(shí)現(xiàn)。如果離開頁面不能停止還需要繼續(xù)執(zhí)行,使用GCD好一點(diǎn)吧,畢竟不會(huì)出現(xiàn)內(nèi)存問題。折中的方法個(gè)人是不喜歡的。大家在項(xiàng)目中一定要具體問題具體對(duì)待,實(shí)現(xiàn)方法有很多,選擇自己認(rèn)為比較好的,沒有問題的才是正確的。

補(bǔ)充 2017-12-19

  • 對(duì)于NSTimer循環(huán)引用的問題可以使用分類解決,代碼如下
  • 新建一個(gè)NSTimer Schedule分類,實(shí)現(xiàn)如下方法。
    + (NSTimer *) timerWithSchedule:(NSTimeInterval)ti block:(void(^)())block userInfo:(id)userInfo repeats:(BOOL)yesOrNo{ NSMutableDictionary *info = [NSMutableDictionary dictionaryWithCapacity:2]; if (userInfo) { info[@"userInfo"] = userInfo; } info[@"timerBlock"] = block; NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:@selector(timerSelector:) userInfo:info repeats:yesOrNo]; return timer; }
    + (void)timerSelector:(NSTimer *)timer { void(^infoBlock)() = timer.userInfo[@"timerBlock"]; if (infoBlock) { infoBlock(); } }
    使用 分類中的初始化方法進(jìn)行NSTimer的初始化即可,只需避免block參數(shù)中的循環(huán)引用即可。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容