iOS 中處理定時(shí)任務(wù)的常用方法

級(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却紧。

注意:

  1. iOS10以前初始化的timer在運(yùn)行期間會(huì)對(duì)target進(jìn)行持有桐臊,因此,在釋放時(shí)需要手動(dòng)調(diào)用invalidate方法晓殊,并置nil断凶;
  2. timer不能在當(dāng)前宿主的dealloc方法中調(diào)用,因?yàn)閠imer沒(méi)有被釋放前巫俺,當(dāng)前宿主不會(huì)執(zhí)行dealloc方法认烁;
  3. 當(dāng)前RunLoop會(huì)切換Mode,因此可能導(dǎo)致timer不是立刻被觸發(fā)介汹。
  4. 在同一線程中却嗡,timer重復(fù)執(zhí)行期間,有其他耗時(shí)任務(wù)時(shí)嘹承,在改耗時(shí)任務(wù)完成前也不會(huì)觸發(fā)定時(shí)窗价,在耗時(shí)任務(wù)完成后,timer的定時(shí)任務(wù)會(huì)繼續(xù)執(zhí)行叹卷。
  5. 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(), ^{
        
    });

注意:

  1. 正在執(zhí)行的 block瘤载,在調(diào)用dispatch_suspend(timer)時(shí)否灾,當(dāng)前block并不會(huì)立即停止而是繼續(xù)執(zhí)行至完成;
  2. dispatch source在掛起時(shí)鸣奔,直接設(shè)置為 nil 或者重新賦值都會(huì)造成crash墨技,需要在activate的狀態(tài)下調(diào)用dispatch_source_cancel(timer)后置為 nil 或者重新賦值;
  3. dispatch_source_cancel方法可以在dispatch_source_set_event_handler中調(diào)用挎狸,即timer可內(nèi)部持有也可外部持有扣汪;
  4. dispatch_resume和dispatch_suspend調(diào)用需成對(duì)出現(xiàn),否則會(huì)crash锨匆;
  5. 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)布局

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恐锣,一起剝皮案震驚了整個(gè)濱河市茅主,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌土榴,老刑警劉巖诀姚,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異玷禽,居然都是意外死亡赫段,警方通過(guò)查閱死者的電腦和手機(jī)呀打,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)糯笙,“玉大人聚磺,你說(shuō)我怎么就攤上這事【嫱瑁” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵蜒蕾,是天一觀的道長(zhǎng)稠炬。 經(jīng)常有香客問(wèn)我,道長(zhǎng)咪啡,這世上最難降的妖魔是什么首启? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮撤摸,結(jié)果婚禮上毅桃,老公的妹妹穿的比我還像新娘。我一直安慰自己准夷,他們只是感情好钥飞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著衫嵌,像睡著了一般读宙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上楔绞,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天结闸,我揣著相機(jī)與錄音,去河邊找鬼酒朵。 笑死桦锄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蔫耽。 我是一名探鬼主播结耀,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼匙铡!你這毒婦竟也來(lái)了饼记?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤慰枕,失蹤者是張志新(化名)和其女友劉穎具则,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體具帮,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡博肋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年低斋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匪凡。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡膊畴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出病游,到底是詐尸還是另有隱情唇跨,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布衬衬,位于F島的核電站买猖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏滋尉。R本人自食惡果不足惜玉控,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狮惜。 院中可真熱鬧高诺,春花似錦、人聲如沸碾篡。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)开泽。三九已至薛窥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間眼姐,已是汗流浹背诅迷。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工罢杉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贡歧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像绍弟,于是被迫代替她去往敵國(guó)和親樟遣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子身笤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容