主要講解Runloop的底層結(jié)構(gòu);
文中使用的 CF源碼是CF-1153.18版本;
Runloop 簡(jiǎn)析
Runloop 補(bǔ)充
1. Runloop 的應(yīng)用范疇
- 定時(shí)器(
timer
)和PerformSelector
; - GCD 的 Asyn Main Queue;
- 事件相應(yīng), 手勢(shì)識(shí)別, 界面刷新 ;
- 網(wǎng)絡(luò)請(qǐng)求;
- AutoreleasePool
2. Runloop 的底層結(jié)構(gòu)
我的都知道Runloop
有 CFRunloopRef
和基于它封裝的CFRunloop
,所以我們只需要探究下CFRunloopRef
的結(jié)構(gòu)即可得知它的底層結(jié)構(gòu);
///源碼中CFRunLoopRef的定義如下
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
===>
///__CFRunLoop的定義如下, 有很多成員, 不過(guò)我們只需要確定它的主要組成是各個(gè) Mod 即可;
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
///每一個(gè) runloop 中都有一個(gè)線程, 確保線程和 runloop 是一一對(duì)應(yīng)的;
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
///Mod的集合
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
===>
///Mod的底層結(jié)構(gòu)如下
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
///集合sources0
CFMutableSetRef _sources0;
///集合souces1
CFMutableSetRef _sources1;
///數(shù)組observers
CFMutableArrayRef _observers;
///數(shù)組timers
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
...
}
關(guān)于Runloop
中常用的幾個(gè)類如下:
CFRunLoopRef
, CFRunLoopModeRef
, CFRunLoopSourceRef
, CFRunLoopObserverRef
, CFRunLoopTimerRef
;
在CFRunLoopModeRef
中的幾個(gè)主要變量的含義:
-
source0
: 處理觸摸事件,或者performSelector:OnThread:
方法; -
source1
: 基于port
的線程通訊(addPort: forMode:
), 系統(tǒng)事件捕捉(例如點(diǎn)擊屏幕后首先是source1
捕捉然后再封裝轉(zhuǎn)發(fā)給source0
處理); -
Timer
: 就是NSTimer
, 調(diào)用performSelector: withObject: afterDelay:
也是類似操作; -
Observer
:監(jiān)聽Runloop的狀態(tài); 主線程UI刷新(例如設(shè)置一個(gè)顏色, 代碼執(zhí)行完后并不會(huì)立即改變, 而是在Runloop
狀態(tài)BeforeWaiting
之前刷新的);另外AutoreleasePool
也是一樣具體什么時(shí)候release
是Runloop
的監(jiān)聽者決定;
如果
Mode
中沒有任何的Source0
,Source
,Timer
,Observer
則Runloop
會(huì)立即退出;
3. Runloop的幾種狀態(tài):
我們都知道Runloop
是一個(gè)事件循環(huán), 整個(gè)循環(huán)有多少種狀態(tài)呢, 源碼種定義如下:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
///即將進(jìn)入 Runloop
kCFRunLoopEntry = (1UL << 0),
///即將處理 Timer
kCFRunLoopBeforeTimers = (1UL << 1),
///即將處理 Source
kCFRunLoopBeforeSources = (1UL << 2),
///即將進(jìn)入休眠
kCFRunLoopBeforeWaiting = (1UL << 5),
///即將喚醒 Runloop
kCFRunLoopAfterWaiting = (1UL << 6),
///即將退出 Runloop
kCFRunLoopExit = (1UL << 7),
///所有狀態(tài)集合
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
-
kCFRunLoopEntry
: 即將進(jìn)入Runloop
; -
kCFRunLoopBeforeTimers
: 即將處理Timer
; -
kCFRunLoopBeforeSources
: 即將處理Sources
; -
kCFRunLoopBeforeWaiting
: 即將進(jìn)入休眠(事情已經(jīng)處理完畢); -
kCFRunLoopAfterWaiting
: 從休眠中喚醒; -
kCFRunLoopExit
: 即將退出Runloop
; -
kCFRunLoopAllActivities
: 所有狀態(tài)集合;
關(guān)于這些狀態(tài)的測(cè)試, 具體詳見補(bǔ)充1;
4. Runloop在日常開發(fā)種的使用場(chǎng)景
具體測(cè)試代碼相見下方補(bǔ)充階段;
- 控制線程的生命周期(線程笨恳郑活);補(bǔ)充2
- 解決
NSTimer
在滑動(dòng)時(shí)失效的問(wèn)題;補(bǔ)充3 - 監(jiān)控應(yīng)用卡頓;補(bǔ)充4
- 性能優(yōu)化;補(bǔ)充5
補(bǔ)充
補(bǔ)充1:Runloop的各個(gè)狀態(tài)測(cè)試
測(cè)試代碼如下:
///創(chuàng)建一個(gè)Observer
CFRunLoopObserverRef CFObserver = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry: {
NSLog(@"Runloop即將進(jìn)入Runloop: kCFRunLoopEntry");
break;
}
case kCFRunLoopBeforeTimers: {
NSLog(@"Runloop即將處理Timer: kCFRunLoopBeforeTimers");
break;
}
case kCFRunLoopBeforeSources: {
NSLog(@"Runloop即將處理Source: kCFRunLoopBeforeSources");
break;
}
case kCFRunLoopBeforeWaiting: {
NSLog(@"Runloop即將進(jìn)入休眠: kCFRunLoopBeforeWaiting");
break;
}
case kCFRunLoopAfterWaiting: {
NSLog(@"Runloop從休眠中喚醒: kCFRunLoopAfterWaiting");
break;
}
case kCFRunLoopExit: {
NSLog(@"Runloop即將退出: kCFRunLoopExit");
}
default:
break;
}
});
///為Runloop添加Observer
CFRunLoopAddObserver(CFRunLoopGetMain(), CFObserver, kCFRunLoopCommonModes);
///CF框架下Create或者Copy的對(duì)象要進(jìn)行釋放
CFRelease(CFObserver);
具體打印結(jié)果由于篇幅太大, 不再貼出, 請(qǐng)自行下載測(cè)試代碼查看打印結(jié)果;
補(bǔ)充2:控制線程的生命周期
通過(guò)啟動(dòng)子線程的Runloop
并添加source1
達(dá)到線程不會(huì) kill
的效果; 詳解請(qǐng)看這篇文章
/*
case3:
子線程中開啟runloop, 為其添加任意的souce0/souce1/timer/observer, 并讓其run, 則此線程由于runloop的關(guān)系就不會(huì)被銷毀;
*/
self.thread3 = [[XThread alloc] initWithBlock:^{
/*
我們知道runloop中如果沒有任何source0/souce1/timer/observer 則runloop會(huì)立即退出;
所以為runloop添加source1, 然后讓runloop執(zhí)行run;
*/
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init ] forMode:NSDefaultRunLoopMode];
/*
注意run的釋義:If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
大致翻譯: 如果沒有souces或者timers添加到runloop中則方法理解退出; 如果有,將在NSDefaultRunLoopMode模式下無(wú)限次執(zhí)行runMode:beforeDate:來(lái)處理添加的souces和timers;
注意: 調(diào)用 runloop 的 run 方法則不再能取消 runloop 即使是調(diào)用了CFRunLoopStop(CFRunLoopGetCurrent()); 也只是停了其中一次循環(huán);
*/
[[NSRunLoop currentRunLoop] run];
}];
[self.thread3 start];
另一種方式
///創(chuàng)建一個(gè)source -- 往 runloop 中添加 source1 source0 timer observer 均可打到效果
///初始化 context
CFRunLoopSourceContext context = {0};
///當(dāng)一個(gè)runloop中沒有事件源處理時(shí), 運(yùn)行完就會(huì)退出;
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
///1. 2. 創(chuàng)建runloop 同時(shí)向runloop中的defaultMode下面添加source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
///3. 啟動(dòng)runloop
while (shouldRun) {
@autoreleasepool {
///令當(dāng)前的runloop運(yùn)行在defaultMode下
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e20, true);
}
}
///某個(gè)時(shí)機(jī), 將靜態(tài)變量shouldRun = NO時(shí), 退出runloop, 進(jìn)而退出線程;
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
補(bǔ)充3: 解決NSTimer在界面拖動(dòng)時(shí)暫時(shí)失效的問(wèn)題
正常情況下, NSTimer
都是添加在DefaultMode
模式下; 一旦界面拖動(dòng)時(shí)Runloop
就會(huì)切換模式到TrackingMode
模式下, NSTimer
就會(huì)暫時(shí)失效;
解決方案:將NStimer
添加到Runloop
的CommonMode
模式下;這樣Runloop就會(huì)同時(shí)處理DefaultMode
和TrackingMode
中的事件;
static NSInteger i = 1;
NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"執(zhí)行次數(shù): %ld", i ++);
}];
[timer fire];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
補(bǔ)充4:詳見性能優(yōu)化部分
補(bǔ)充5:相見性能優(yōu)化部分
參考文章和下載鏈接
Apple 一些源碼的下載地址