developer:hi,runloop 初次見面,交個朋友吧?
runloop :你好,很高興認識你!
developer:我先自我介紹一下,我叫iOS Developer,你呢?
runloop :我叫runloop,你的有些同伴叫我跑圈,我覺得叫我循環運行比較合適!
developer:我聽我的同伴說你有點不是很好相處啊,是不是真的?
runloop :不會吧,我只是喜歡在幕后工作,并不是很愛拋頭露面而已!
developer:那你的主要工作內容都是哪些呢?
runloop :我啊?主要是為了讓程序一直處于運行狀態,同時在運行的狀態下處理一系列的事件,比如觸摸事件、定時器事件、selecter事件,在這樣的高強度的工作下還得維持良好的狀態!
developer:聽起來很屌的樣子!跟我說說你跟線程的關系吧,聽說你兩是生死相依的兄弟!
runloop :線程啊,我兩誰也離不開誰,他出生了我也就誕生了,當他被銷毀的時候我也就撲街了。
developer:那平時我們拜訪線程挺容易的,要怎么去拜訪你呢?
runloop :有兩種方式
1、Foundation框架下的NSRunLoop
2、Core Foundation框架下得CFRunLoopRef
不論NSRunLoop和CFRunLoopRef都代表著我
[NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象
CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象
看到這張圖沒?
這張圖就代表著一個完整的runloop,每一個runloop中都包含以下5個類:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
如果沒有這5個類,runloop將會直接退出
developer:能詳細的介紹以下他們嗎?
runloop :當然。
CFRunLoopModeRef
CFRunLoopModeRef 類并沒有對外暴露,只是通過 CFRunLoopRef 的接口進行了封裝。
CFRunLoopModeRef代表RunLoop的運行模式
一個 RunLoop 包含若干個 Mode,每個 Mode 又包含若干個 Source/Timer/Observer。每次調用 RunLoop 的主函數時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode。如果需要切換 Mode,只能退出 Loop,再重新指定一個 Mode 進入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。
系統默認注冊了5個Mode:
kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到
kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode
CFRunLoopSourceRef
CFRunLoopSourceRef 是事件產生的地方。Source有兩個版本:Source0 和 Source1。
? Source0 只包含了一個回調(函數指針),它并不能主動觸發事件。使用時,你需要先調用 CFRunLoopSourceSignal(source),將這個 Source 標記為待處理,然后手動調用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件。
? Source1 包含了一個 mach_port 和一個回調(函數指針),被用于通過內核和其他線程相互發送消息。
CFRunLoopObserverRef
CFRunLoopObserverRef 是觀察者,每個 Observer 都包含了一個回調(函數指針),當 RunLoop 的狀態發生變化時,觀察者就能通過回調接受到這個變化。可以觀測的時間點有以下幾個:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出Loop
};
上面的 Source/Timer/Observer 被統稱為 mode item,一個 item 可以被同時加入多個 mode。但一個 item 被重復加入同一個 mode 時是不會有效果的。如果一個 mode 中一個 item 都沒有,則 RunLoop 會直接退出,不進入循環。
使用
- (void)observer{
// 創建observer CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"----監聽到RunLoop狀態發生改變---%zd", activity);
});
// 添加觀察者:監聽RunLoop的狀態
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 釋放Observer
CFRelease(observer);}
特別注意
/* CF的內存管理(Core Foundation)
1.凡是帶有Create、Copy、Retain等字眼的函數,創建出來的對象,都需要在最后做一次release * 比如CFRunLoopObserverCreate
2.release函數:CFRelease(對象);
*/
CFRunLoopTimerRef
CFRunLoopTimerRef 是基于時間的觸發器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一個時間長度和一個回調(函數指針)。當其加入到 RunLoop 時,RunLoop會注冊對應的時間點,當時間點到時,RunLoop會被喚醒以執行那個回調。
RunLoop 的內部邏輯
根據蘋果在文檔里的說明,RunLoop 內部的邏輯大致如下:
可以看到,實際上 RunLoop 就是這樣一個函數,其內部是一個 do-while 循環。當你調用 CFRunLoopRun() 時,線程就會一直停留在這個循環里;直到超時或被手動停止,該函數才會返回。
developer:那我應該怎么樣勞煩你去處理一些問題呢?
runloop :解密-神秘的RunLoop這里有記錄怎么去使用runloop
developer:如何才能與你深交呢?
runloop :深入理解RunLoop