概要
在iOS開發(fā)中狼忱,Runtime與RunLoop應該是iOS Developer技術(shù)進階時需要掌握的兩方面知識,相對來說一睁,它倆也比較接近底層钻弄,就現(xiàn)在環(huán)境來看,面試時也比較容易問到者吁。
關(guān)于這兩項窘俺,網(wǎng)上的文章大多是講了很多知識點,然而實際開發(fā)中用不到复凳,那就找一個很簡單的問題來把這兩項知識實戰(zhàn)一下
鎖屏或切換至后臺時計時器停止
相信大家都遇到過的問題:在注冊頁面有一個NSTimer實現(xiàn)的驗證碼倒計時的按鈕瘤泪,在手機切出app,把app在后臺掛起時育八,倒計時是停止的对途,如你切出時時間剩余50秒,當你從后臺返回時髓棋,倒計時依然是50秒实檀。
什么原因惶洲?
如果你有關(guān)于RunLoop的知識,你應該知道
- 每一個線程都有一個自己的RunLoop膳犹,他們的關(guān)系是一一對應的
- NSTimer 其實就是 CFRunLoopTimerRef
上面兩點不懂可以來這里 -- 深入理解RunLoop
CFRunLoopTimerRef 是基于時間的觸發(fā)器恬吕,它和 NSTimer 是toll-free bridged 的,可以混用须床。其包含一個時間長度和一個回調(diào)(函數(shù)指針)币呵。當其加入到 RunLoop 時,RunLoop會注冊對應的時間點侨颈,當時間點到時余赢,RunLoop會被喚醒以執(zhí)行那個回調(diào)
簡單來說就是NSTimer在初始化以后,必須被加入到RunLoop中才會生效
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
使用此方法初始化時哈垢,會自動將我們創(chuàng)建的NSTimer加入到RunLoop中
造成NSTimer停止的原因妻柒,是當app在后臺掛起時,線程同時被掛起耘分,RunLoop也就被掛起举塔,而NSTimer是運行在RunLoop中的
,所以在app掛起時求泰,NSTimer就同時停止了工作央渣。
關(guān)于NSTimer想更深入的可以參考iOS開發(fā)之 不要告訴我你會用NSTimer!
怎么解決?
知道了NSTimer停止的原因是因為線程不活躍
那解決NSTimer停止的方法就是app在掛起時渴频,讓其所在RunLoop的線程處于活躍狀態(tài)
我們需要的是UIApplication
的:
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^ __nullable)(void))handler
這個方法芽丹,這個方法就是開啟一個后臺任務,使線程處于活躍狀態(tài)便于執(zhí)行此后臺任務卜朗,線程活躍了拔第,NSTimer也就可以繼續(xù)跑下去不會停止,當然這個方法只能讓主線程活躍180秒
场钉,
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^ __nullable)(void))handler
與
- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)identifier
是一對蚊俺,你調(diào)用了前者開啟了一個后臺任務,就要調(diào)用后者來結(jié)束這個任務
// AppDelegate 中聲明一個標識
@property (nonatomic, unsafe_unretained) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
然后在app進入后臺時
- (void)applicationDidEnterBackground:(UIApplication *)application{
// 返回一個任務標識
self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void){
// 申請的時間到期后進入這里逛万,即馬上將被掛起泳猬,不再活躍
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
// 將標識符標記為 UIBackgroundTasksInvalid,任務結(jié)束
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}];
}
當然如果app切回來的話也要把任務結(jié)束
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
到此我們解決了一個關(guān)于定時器停止的問題,看似解決問題的是開啟/結(jié)束 后臺任務的兩個方法宇植,但實際上是我們運用了RunLoop的知識來解決的得封,這是RunLoop知識運用在實際開發(fā)中的一個案例
對了上述我們用到的方法也可以用于解決程序掛起時的復雜操作
比如需要在程序掛起時向服務器post一些數(shù)據(jù),以前做的一款產(chǎn)品就是要收集各種操作信息当纱,收集用戶的操作路徑啊云云呛每,包括用戶切換至后臺這樣的操作都要收集踩窖。
用這個方法同樣可以解決坡氯,但是要注意:
post要用同步方法,保證在主線程里進行