級(jí)別: ★☆☆☆☆
標(biāo)簽:「iOS」「定時(shí)任務(wù) 」
作者: dac_1033
審校: QiShare團(tuán)隊(duì)
在項(xiàng)目開(kāi)發(fā)中,經(jīng)常會(huì)在代碼中處理一些需要延時(shí)或定時(shí)執(zhí)行的任務(wù),iOS 中處理定時(shí)任務(wù)的方法包括 performSelector 方法弟塞、NSTimer稿械、GCD、CADisplayLink乏沸,其本質(zhì)都是通過(guò)RunLoop來(lái)實(shí)現(xiàn)淫茵,下面我們就對(duì)這幾個(gè)常用方法做一些總結(jié)(其中CADisplayLink放在后續(xù)文章中介紹)。
1. performSelector方法
在NSRunLoop.h中有對(duì)NSObject類(lèi)的擴(kuò)展方法蹬跃,簡(jiǎn)單易用:
@interface NSObject (NSDelayedPerforming)
// 延時(shí)執(zhí)行某個(gè)方法匙瘪,只能帶一個(gè)參數(shù)
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
// 取消某個(gè)延時(shí)操作
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
// 取消一個(gè)target下的所有延時(shí)操作
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
@end
注:cancel操作,只能取消掉當(dāng)前還沒(méi)有被執(zhí)行的Selector蝶缀。
2. NSTimer
NSTimer 是最常使用的定時(shí)器丹喻,使用方式簡(jiǎn)單,NSTimer 是也通過(guò)添加到RunLoop中被觸發(fā)并進(jìn)行工作的翁都,橋接 CFRunLoopTimerRef碍论。NSTimer中定義的常用方法如下:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep NS_DESIGNATED_INITIALIZER;
以下是初始化NSTimer的不同方式:
// 自動(dòng)加入currentRunLoop
self.timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(timerRuning) userInfo:nil repeats:YES];
//self.timer = [NSTimer scheduledTimerWithTimeInterval:5.0 repeats:YES block:^(NSTimer * _Nonnull timer) { }];
// 手動(dòng)加入RunLoop
self.timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(timerRuning) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
// 指定timer觸發(fā)時(shí)刻
NSTimeInterval timeInterval = [self timeIntervalSinceReferenceDate] + 30;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:timeInterval];
self.timer = [[NSTimer alloc] initWithFireDate:newDate interval:5 target:self selector:@selector(timerRuning) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
如果當(dāng)前界面中有UITableView,則在 UITableView 在滾動(dòng)過(guò)程中柄慰,上述代碼中的定時(shí)器到了時(shí)間并沒(méi)有觸發(fā)鳍悠。根據(jù)RunLoop的相關(guān)知識(shí),同一時(shí)刻 RunLoop 只運(yùn)行在一種 Mode 上先煎,并且只有這個(gè) Mode 相關(guān)聯(lián)的源或定時(shí)器會(huì)被傳遞消息贼涩,mainRunLoop 一般處于 NSDefaultRunLoopMode,但是在滾動(dòng)或者點(diǎn)擊事件等觸發(fā)時(shí)薯蝎,mainRunLoop 切換至 NSEventTrackingRunLoopMode 遥倦,而上面 timer 被加入的正是 NSDefaultRunLoopMode (未指明也默認(rèn)加入默認(rèn)模式),所以滑動(dòng)時(shí)未觸發(fā)定時(shí)操作占锯。
解決方法:添加timer到mainRunLoop的NSRunLoopCommonMode中或者子線程中袒哥,需要注意的是加入子線程時(shí)要手動(dòng)開(kāi)啟并運(yùn)行子線程的RunLoop。
self.timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(timerRuning) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
NSRunLoopCommonMode這是一組可配置的常用模式消略。將輸入源與此模式相關(guān)聯(lián)也會(huì)將其與組中的每個(gè)模式相關(guān)聯(lián)堡称。對(duì)于Cocoa應(yīng)用程序,此集合默認(rèn)包括NSDefaultRunLoopMode艺演,NSPanelRunLoopMode和NSEventTrackingRunLoopMode却紧。
注意:
- iOS10以前初始化的timer在運(yùn)行期間會(huì)對(duì)target進(jìn)行持有桐臊,因此,在釋放時(shí)需要手動(dòng)調(diào)用invalidate方法晓殊,并置nil断凶;
- timer不能在當(dāng)前宿主的dealloc方法中調(diào)用,因?yàn)閠imer沒(méi)有被釋放前巫俺,當(dāng)前宿主不會(huì)執(zhí)行dealloc方法认烁;
- 當(dāng)前RunLoop會(huì)切換Mode,因此可能導(dǎo)致timer不是立刻被觸發(fā)介汹。
- 在同一線程中却嗡,timer重復(fù)執(zhí)行期間,有其他耗時(shí)任務(wù)時(shí)嘹承,在改耗時(shí)任務(wù)完成前也不會(huì)觸發(fā)定時(shí)窗价,在耗時(shí)任務(wù)完成后,timer的定時(shí)任務(wù)會(huì)繼續(xù)執(zhí)行叹卷。
- dispatch_source_set_timer中設(shè)置啟動(dòng)時(shí)間舌镶,dispatch_time_t可通過(guò)兩個(gè)方法生成:dispatch_time 和 dispatch_walltime
3. GCD定時(shí)器
我們也可以通過(guò)GCD中的方法實(shí)現(xiàn)定時(shí)器來(lái)處理定時(shí)任務(wù),實(shí)現(xiàn)的代碼邏輯如下:
// 1. 創(chuàng)建 dispatch source豪娜,指定檢測(cè)事件為定時(shí)
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue("Timer_Queue", 0));
// 2. 設(shè)置定時(shí)器啟動(dòng)時(shí)間餐胀、間隔
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
// 3. 設(shè)置callback
dispatch_source_set_event_handler(timer, ^{
NSLog(@"timer fired");
});
dispatch_source_set_event_handler(timer, ^{
//取消定時(shí)器時(shí)一些操作
});
// 4. 啟動(dòng)定時(shí)器(剛創(chuàng)建的source處于被掛起狀態(tài))
dispatch_resume(timer);
// 5. 暫停定時(shí)器
dispatch_suspend(timer);
// 6. 取消定時(shí)器
dispatch_source_cancel(timer);
timer = nil;
當(dāng)我們想要timer只是延時(shí)執(zhí)行一次時(shí),只調(diào)用以下方法即可:
// 在主線程中延時(shí)5s中執(zhí)行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
注意:
- 正在執(zhí)行的 block瘤载,在調(diào)用dispatch_suspend(timer)時(shí)否灾,當(dāng)前block并不會(huì)立即停止而是繼續(xù)執(zhí)行至完成;
- dispatch source在掛起時(shí)鸣奔,直接設(shè)置為 nil 或者重新賦值都會(huì)造成crash墨技,需要在activate的狀態(tài)下調(diào)用dispatch_source_cancel(timer)后置為 nil 或者重新賦值;
- dispatch_source_cancel方法可以在dispatch_source_set_event_handler中調(diào)用挎狸,即timer可內(nèi)部持有也可外部持有扣汪;
- dispatch_resume和dispatch_suspend調(diào)用需成對(duì)出現(xiàn),否則會(huì)crash锨匆;
- dispatch source會(huì)比 NSTimer 更精準(zhǔn)一些崭别。
參考文章,感謝??...
推薦文章:
算法小專(zhuān)欄:貪心算法
iOS 快速實(shí)現(xiàn)分頁(yè)界面的搭建
iOS 中的界面旋轉(zhuǎn)
iOS 常用布局方式之Frame
iOS 常用布局方式之Autoresizing
iOS 常用布局方式之Constraint
iOS 常用布局方式之StackView
iOS 常用布局方式之Masonry
iOS UIButton根據(jù)內(nèi)容自動(dòng)布局