寫這篇文章開始之前,我都不知道runloop是什么東西,如果從字面的意思翻譯應(yīng)該是一直循環(huán)的跑,懷疑可能和死鎖有關(guān)系,可是死鎖具體是怎么回事,我只是記得有這個說法,也發(fā)現(xiàn)了一個自己不懂的知識。
初識runloop
我在網(wǎng)上看了一下@sunnnyxx 關(guān)于runloop的視頻.了解了一下runloop相關(guān)知識,也去網(wǎng)絡(luò)上看各種關(guān)于runloop的講述。
我們一般程序就是執(zhí)行一個線程,是一條直線.有起點終點.而runloop就是一直在線程上面畫圓圈,一直在跑圈,除非切斷否則一直在運行。網(wǎng)上說的比喻很好,直線就像曇花一現(xiàn)一樣,圓就像OS,一直運行直到你關(guān)機為止。
在我們學(xué)習(xí)iOS生命周期里面都會存在銷毀的過程,但是屏幕好像一直能接收各種指令,感覺很像runloop的功效,好像這些是和頂層UIKit無關(guān),IOS架構(gòu)最底層是Core OS,我分析應(yīng)該是蘋果封裝好了,只是我們看不到源碼而已。
為什么要使用runloop
@sunnnyxx 在視頻介紹了四個作用:
- 使程序一直運行接受用戶輸入
- 決定程序在何時應(yīng)該處理哪些Event
- 調(diào)用解耦(對于編程經(jīng)驗為0的完全沒搞懂這個意思,解釋為Message Queue)
- 節(jié)省CPU時間
這段視頻我覺得不太適合小白去看,因為好多概念還沒有融會貫通,也沒有理解透徹。但是既然看了,就得總結(jié)一下,至少產(chǎn)生一個樹突先,留下一個問號,未來把問號變成嘆號。
回到開始的疑問,為什么要使用RunLoop,一般情況下我們是沒必要去啟動線程的RunLoop,除非需要在一個單獨的線程長久的檢測某個事件,就像視頻里面提到的類似微信的語音功能,見一個RunLoop專門負(fù)責(zé)監(jiān)聽說話的線程。看需求而定了。
CFRunLoopSource
Source是RunLoop的數(shù)據(jù)源抽象類,類似IOS中的protocol
RunLoop定義兩個Version的Source
- Source0:處理App內(nèi)部事件,App自己負(fù)責(zé)管理(觸發(fā)),如UIEvent,CFSocket
- Source1:由RunLoop和內(nèi)核管理,Mach port驅(qū)動 如CFMach、CFMessage
CFRunLoopObserver
向內(nèi)部報告RunLoop當(dāng)前狀態(tài)的更改 CAAnimation
RunLoopObserver 與 Autorelease Pool
UIKit通過RunLoopObserver在RunLoop兩次Sleep間對AutoreleasePool進(jìn)行pop和push,將這次Loop中產(chǎn)生的Autorelease對象釋放。(好像swift中沒有關(guān)于釋放的問題)
CFRunLoopMode
RunLoop在同一時段只能且必須在一種特定Mode下Run
更換Mode時, 需要暫停當(dāng)前的Loop,然后重啟新的Loop
- NSDefalutRunLoopMode? ? ? 默認(rèn)狀態(tài).空閑狀態(tài)
- UITrackingRunLoopMode? ? ?滑動ScrollView
- UIInitializationRunLoopMode? ? 私有,App啟動時
- NSRunLoopCommonModes? ? ?默認(rèn)包括上面第一和第二
UITrackingRunLoopMode 與 NSTimer
默認(rèn)情況下NSTimer被加入NSDefalutRunLoopMode
如果想NSTimer受到組件或者動畫影響 添加到NSRunLoopCommonModes(OC代碼如下:)
[[NSRunLoop currentRunLoop]addTimer:timer...forMode:NSRunLoopCommonModes];
swift版代碼:
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
RunLoopMode切換
NSDefaultRunLoopMode->UITrackingRunLoopMode->NSDefalutRunLoopMode
RunLoop的掛起與喚醒
- 制定用于喚醒的
mach_port
端口
- 調(diào)用
mach_msg
監(jiān)聽喚醒端口,被喚醒前,系統(tǒng)內(nèi)核將這個線程掛起,停留在mach_msg_trap
- 由另外一個線程(或另一個進(jìn)程中的某個線程)向內(nèi)核發(fā)送這個端口的msg后,trap狀態(tài)被喚醒,RunLoop繼續(xù)開始干活
AFNetWorking 中創(chuàng)建RunLoop
創(chuàng)建一個常駐服務(wù)線程的很好方法
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefalutRunLoopMode]//一直活著
[runLoop run];
swift版代碼
var loop = NSRunLoop.currentRunLoop()
loop.addPort(NSMachPort(), forMode: NSDefaultRunLoopMode)
loop.run()
一個TableView延遲加載圖片的新思路
[self.avatarImageView performSelector:@selector(serImage:)
withObjetc:downloadedImage
afterDelay:0
inModes:@[NSDefaultRunLoopMode]]
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread =
[[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
這個代碼無法轉(zhuǎn)換成swift,可能是我沒想到辦法,大家誰找到了請評論,謝謝了。
讓Crash的App回光返照 只針對Signal Crash
CFRunLoopRef runloop = CFRunLoopGetCurrent();
NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));
while(1){
for (NSString *mode in allModes){
CFRunLoopInMode((CFStringRef)mode,0.001,false);
}
}
RunLoop事件隊列
每次運行run loop,你線程的run loop對會自動處理之前未處理的消息,并通知相關(guān)的觀察者。具體的順序如下:
- 通知觀察者run loop已經(jīng)啟動
- 通知觀察者任何即將要開始的定時器
- 通知觀察者任何即將啟動的非基于端口的源
- 啟動任何準(zhǔn)備好的非基于端口的源
- 如果基于端口的源準(zhǔn)備好并處于等待狀態(tài),立即啟動;并進(jìn)入步驟9。
- 通知觀察者線程進(jìn)入休眠
- 將線程置于休眠直到任一下面的事件發(fā)生:
- 某一事件到達(dá)基于端口的源
- 定時器啟動
- Run loop設(shè)置的時間已經(jīng)超時
- run loop被顯式喚醒
- 通知觀察者線程將被喚醒。
- 處理未處理的事件
- 如果用戶定義的定時器啟動,處理定時器事件并重啟run loop。進(jìn)入步驟2
- 如果輸入源啟動,傳遞相應(yīng)的消息
- 如果run loop被顯式喚醒而且時間還沒超時,重啟run loop。進(jìn)入步驟2
- 通知觀察者run loop結(jié)束。
異步測試
- (BOOL)runUntilBlock:(BOOL(^)())block timeout:(NSTimeInterval)timeout{
__block Boolean fulfilled = NO;
void (^beforeWaiting) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) =
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
fulfilled = block();
if (fulfilled) {
CFRunLoopStop(CFRunLoopGetCurrent());
}
};
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, beforeWaiting);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// Run!
CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false);
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
CFRelease(observer);
return fulfilled;
}
總結(jié)一下
基本上對于RunLoop,我只能說我只有簡單的模糊印象,但是不理解,也不太清楚在現(xiàn)實如何使用,因為實踐才能知道如何做,真的在腦里形成了樹突,希望在未來搞定,研究透徹。看這些代碼時候我發(fā)現(xiàn)我的基礎(chǔ)知識還需要鞏固。感謝不相識的孫源。
聲明一點:不要只去收藏,去看看代碼有沒有問題,知識有沒有解釋錯誤的,交流學(xué)的更快。