相關連接:
深入理解RunLoop
IOS---實例化講解RunLoop
iOS知識點整理-RunLoop
RunLoop的前世今生
RunLoop的概念
RunLoop是一個時間處理環,系統利用這個時間處理環來安排事物,協調輸入的各種時間.RunLoop的目的是讓你的線程在有工作的時候忙碌,沒有工作的時候休眠(和線程相關)
一般來講,一個線程只能執行一個任務,執行完成后線程就會退出.....但是實際情況是,我們希望有一個機制,,讓線程能隨時處理時間但并不退出....當我們給他發送退出指令是它才退出...
在iOS中RunLoop就是用來實現這種機制的...這種機制的關鍵點在于:如何處理時間和消息,讓線程在沒有處理消息的時候休眠以避免資源占用,在有消息到來時立刻被喚醒
RunLoop實際上就是一個對象...這個對象管理其需要處理的時間和消息, 并提供了一個入口函數來執行上訴邏輯....線程執行了這個函數后...就會一直處于這個函數內部 "接收消息 ->等待 -> 處理"的循環中,知道這個循環結束(比如傳入quit消息),函數返回..
iOS提供了兩個這樣的對象 NSRunLoop和CFRunLoopRef
RunLoop和線程的關系
線程和RunLoop之間是一一對應的,其關系是保存在一個全局的Dictionary里....線程創建之后是沒有RunLoop的(主線程除外),RunLoop的創建時發生在第一次獲取時.
蘋果不允許世界創建RunLoop, 但是可以通過[NSRunLoop currentRunLoop]或者CFRunLoopGetCurrent()來獲取(如果沒有就會自動創建一個)
RunLoop對外的接口
CoreFondation里關于RunLoop有4個類:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
RunLoop共包含5個類,但公開的只有Source,Timer,Observer相關的三個類.
1.RunLoop Modes
一個RunLoop包含若干個Mode,每個Mode又包含若干個Source/Timer/Observer每次調用RunLoop的函數是,只能指定其中一個mode,這個mode被稱為CurrentMode....如果需要切換Mode..只能退出Loop, 再重新指定一個Mode進入.這樣做是為了分割開不通組的Source/Timer/Observer,讓其相互不影響.
- kCFDefaultRunLoopMode App的默認Mode,通常主線程是在這個Mode下運行
- UITrackingRunLoopMode 界面跟蹤Mode,用于ScrollView追蹤觸摸滑動,保證界面滑動時不受其他Mode影響
- UIInitializationRunLoopMode 在剛啟動App時第進入的第一個Mode,啟動完成后就不再使用
- GSEventReceiveRunLoopMode 接受系統事件的內部Mode,通常用不到
- kCFRunLoopCommonModes 這是一個占位用的Mode,不是一種真正的Mode
2.RunLoop Timer
基于時間的觸發器,基本上就是說NSTimer
在新建NSTimer之后,要把timer添加到RunLoop中,否則timer不會執行.
NSTimer *timer = [NSTimer timerWithTimerInterval:5.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
add到相應的Mode定時器的方法就只能在相應的Mode下才生效,
也可以把Mode設置為NSRunLoopCommonModes,就可以再默認模式和追蹤模式下都能運行.
如果是通過scheduledTimerWithTimeInterval創建的NSTimer,默認添加到RunLoop的DefaultMode中,所以他會自動運行.
當其加入到RunLoop時,RunLoop會注冊對應的時間點,當時間點到時,RunLoop會被喚醒以執行那個回調.如果線程阻塞或者不在這個Mode下,觸發點將不會執行,一直等到下一個周期時間點的觸發.
3.RunLoop Source
CFRunLoopSourceRef是事件源,比如外部的觸摸,點擊事件和系統內部進程間的通信等.
Source有兩個版本:Source0和Source1
Source0:非基于Port的,只包含了一個回調,它并不能主動觸發時間.使用時,你需要先調用CFRunLoopSourceSignal(source),講這個Source標記為待處理,然后手動調用CFRunLoopWakeUp(runloop)來喚醒RunLoop.讓其處理這個事件.
Source1:基于Port的,包含了一個mach_port和一個回調,被用于通過內核和其他線程相互發送消息.這種Source能主動喚醒RunLoop的線程,后面講到AFNetworking創建的常駐線程就是在線程中添加一個NSport來實現的.
4.RunLoop Observer
CFRunLoopObserverRef是觀察者,每個Observer都包含一個回調,當RunLoop的狀態發生變化時,觀察者就能通過回調接收到這個變化.
3.RunLoop的運行機制
RunLoop的實際應用
1.AutoreleasePool
app啟動后,系統啟動主線程并創建了RunLoop,在主線程里注冊了兩個observer,回調都是_wrapRunLoopWithAutoreleasePoolHandle()
第一個observer監聽一個事件:即將進入Loop(kCFRunLoopEntry),
調用_objc_autoreleasePoolPush()創建一個棧自動釋放池,這個優先級最高,保證創建釋放池在其他操作之前
第二個observer監聽兩個事件:準備進入休眠(kCFRunLoopBeforeWaiting)和即將退出Loop(kCFRunLoopExit)
進入休眠釋放舊的池并創建新的池,退出是釋放掉自動釋放池.
在主線程中執行代碼一般都是寫在事件回調或Timer回調中的,這些回調都被加入了主線程的自動釋放池中,所以在ARC模式下我們不用關心對象什么時候釋放,也不用去創建和管理pool.(如果事件不在主線程中要注意創建自動釋放池,否則可能會出現內存泄漏)
2.NSTimer優化使用
開放中常見的現象,在界面上有個UIScrollView控件,如果此時還要一個定時器在執行一個事件,你會發現當你滾動scrollView的時候,定時器會失效..
因為timer用scheduledTimerWithTimeInterval:初始化的時候默認關聯為DefaultMode,在主線程UITrackingRunLoopMode優先級最高,在用戶拖動控件時,主線程的RunLoop運行在UITrackingRunLoopMode下,因此系統不會立即執行Default Mode下的事件.
解決方法1.把當前timer加入到UITrackingRunLoopMode或者kCFRunLoopCommonModes中.
解決方法2.使用不受RunLoop影響的GCD創建定時器.
3.滑動時加載圖片,滑動不流暢的問題.
用戶滑動scrollview的過程中加載圖片,由于UI的操作都是在主線程進行的,會造成滑動不流暢的問題,這個時候我們就需要在滑動的不加載圖片,等滑動操作完成在進行加載圖片的操作.
UIImage *downloadImage = ...
[self.imageView performSelector:@selector(setImage:)
withObject: downloadImage
afterDelay:3.0
inModes:@[NSDefaultRunLoopMode]];
講setImage放到DefaultMode里確定UITrackingRunLoopMode下該操作不會被執行...
4.網絡請求
AFN每次進行的網絡操作,開始,暫停,取消操作時,都將相應的執行任務扔進了自己創建的線程RunLoop中進行處理,從而避免造成主線程的阻塞.
5.處理崩潰讓程序繼續運行
我們都知道,如果App運行遇到 Exception 就會直接崩潰并且退出,其實真正讓應用退出的并不是產生的異常,而是當產生異常時,系統會結束掉當前主線程的 RunLoop ,RunLoop 退出主線程就退出了,所以應用才會退出。明白這個道理,去完成這個“不可能的任務”就很簡單了。
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!isQuit){
for (NSString *mode in (__bridge NSArray *)allModes) {
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
CFRelease(allModes);
把上面的代碼添加到 Exception 的handle方法中,此時創建了一個 RunLoop ,讓這個 RunLoop 在所有的 Mode 下面一直不停的跑,保證主線程不會退出,我們的應用也就存活下來了。