一.基本概念
1.runloop是一個(gè)事件驅(qū)動(dòng)的循環(huán),收到事件就去處理,沒有事件就進(jìn)入睡眠.
2.應(yīng)用一啟動(dòng)主線程被創(chuàng)建后,主線程對(duì)應(yīng)的runloop也被創(chuàng)建,runloop也保證了程序能夠一直運(yùn)行.之后創(chuàng)建的子線程默認(rèn)是沒有runloop的,只有當(dāng)調(diào)用[NSRunLoop currentRunLoop]去獲取的時(shí)候才被創(chuàng)建.
3.runloop的模式:
mode一共五種:
- NSDefaultRunLoopMode 默認(rèn)
- UITrackingRunLoopMode UI專用
- UIInitializationRunLoopMode 在剛啟動(dòng)App時(shí)第進(jìn)入的第一個(gè)Mode君丁,啟動(dòng)完成后就不再使用
- GSEventReceiveRunLoopMode 接受系統(tǒng)事件的內(nèi)部Mode
- NSRunLoopCommonModes 這是一個(gè)占位用的Mode而线,不是一種真正的Mode
首先是這樣一個(gè)例子:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(tim) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
UITextView *tv = [[UITextView alloc]init];
tv.backgroundColor = [UIColor lightGrayColor];
tv.text = @"aasdasdsdasdsdasasdasdasdasdasdasdasaasdasdsdasdsdasasdas";
tv.frame = CGRectMake(0, 100, 50, 50);
[self.view addSubview:tv];
timer添加在default模式中,默認(rèn)是正常執(zhí)行的,當(dāng)滑動(dòng)textView的時(shí)候,timer就停下來(lái)了.
模式類似于跑道,一個(gè)runloop同時(shí)只能在一個(gè)跑道上進(jìn)行,并且模式有優(yōu)先級(jí),UITrackingRunLoopMode是UI模式,是優(yōu)先級(jí)最高的模式,當(dāng)runloop遇到事件的時(shí)候,會(huì)根據(jù)事件指定的模式,去切換跑到,UITrackingRunLoopMode的優(yōu)先級(jí)最高,所以就切換到了這個(gè)模式上,這時(shí)候其他模式下的事件就不會(huì)被執(zhí)行.
并且UITrackingRunLoopMode只能被UI事件喚醒,如果將上面的timer指定的模式改成UI模式,那么默認(rèn)是不會(huì)執(zhí)行的,但是當(dāng)滑動(dòng)textView時(shí),跑到就切換到了UI模式,timer就可以執(zhí)行了,一旦停止滑動(dòng),timer又會(huì)停 下來(lái).
NSRunLoopCommonModes實(shí)質(zhì)就是NSDefaultRunLoopMode和UITrackingRunLoopMode,等同于在兩個(gè)模式上都添加一遍,不管是那個(gè)跑道,都會(huì)遇到timer的事件.
NSThread *th = [[NSThread alloc]initWithBlock:^{
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(tim) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"runloop后面");
}];
[th start];
在子線程獲取runloop,由于UI只在主線程進(jìn)行,主線程的runloop和子線程的runloop自然是沒有關(guān)系的,此時(shí)使用NSDefaultRunLoopMode不再受到UI事件影響.
一旦調(diào)用run方法,runloop就會(huì)開始執(zhí)行,子線程也不會(huì)被銷毀,run后面的代碼在runloop停下來(lái)之前也不會(huì)被執(zhí)行.
NSThread *th = [[NSThread alloc]initWithBlock:^{
self.shouldStop = NO;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(tim) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
while(!self.shouldStop){
[[NSRunLoop currentRunLoop]runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.0001]];
}
NSLog(@"runloop后面");
}];
[th start];
換成runUntilDate方法,runloop只會(huì)執(zhí)行一段時(shí)間,放在while循環(huán)里就可以進(jìn)行控制.
二.Runloop的結(jié)構(gòu)
一個(gè)RunLoop包含了多個(gè)Mode客峭,也就是跑道,每個(gè)Mode又包含了若干個(gè)Source/Timer/Observer倍踪。每次調(diào)用 RunLoop的主函數(shù)時(shí)竟终,只能指定其中一個(gè)Mode,這個(gè)Mode被稱作CurrentMode退疫。如果需要切換 Mode参歹,只能退出Loop,再重新指定一個(gè)Mode.
runloop的結(jié)構(gòu)如下:
struct __CFRunLoop {
pthread_t _pthread;//線程
CFMutableSetRef _commonModes; // commonModes下的兩個(gè)mode(kCFRunloopDefaultMode和UITrackingMode)
CFMutableSetRef _commonModeItems; // 在commonModes狀態(tài)下運(yùn)行的對(duì)象(例如Timer)
CFMutableSetRef _modes; // 運(yùn)行的所有模式(CFRunloopModeRef類)
CFRunLoopModeRef _currentMode;//在當(dāng)前l(fā)oop下運(yùn)行的mode
...
};
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
Source/Timer/Observer是事件源, 也就是要執(zhí)行的任務(wù),RunLoop里面保存的是RunLoopMode岔霸,而RunLoopMode中保存的才是實(shí)際執(zhí)行的任務(wù).
@property (nonatomic, strong) dispatch_source_t timer;
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"lala");
});
dispatch_resume(self.timer);
這段代碼使用GCD創(chuàng)建了一個(gè)timer,這里就出現(xiàn)了source
source分為兩種
- source0:觸摸事件薛躬,PerformSelectors,非基于Port的
- source1:系統(tǒng)內(nèi)核事件,基于Port的線程間通信
observer:
observer是runloop的監(jiān)聽者,能夠監(jiān)聽這幾種狀態(tài)
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即將進(jìn)入
kCFRunLoopBeforeTimers = (1UL << 1),//即將處理timer
kCFRunLoopBeforeSources = (1UL << 2),//即將處理source
kCFRunLoopBeforeWaiting = (1UL << 5),//即將進(jìn)入睡眠
kCFRunLoopAfterWaiting = (1UL << 6),//剛從睡眠中蘇醒
kCFRunLoopExit = (1UL << 7),//即將退出
kCFRunLoopAllActivities = 0x0FFFFFFFU //所有狀態(tài)
};
監(jiān)聽runloop需要使用到CoreFoundation 下的CFRunLoop,首先看創(chuàng)建CFRunLoopObserverRef的代碼
CFRunLoopRef runloop = CFRunLoopGetCurrent();
CFRunLoopObserverContext context = {
0,
(__bridge void *)(self),
&CFRetain,
&CFRelease,
NULL
};
static CFRunLoopObserverRef observer;
observer = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, 0, &callBack, &context);
CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
- 首先需要獲取CFRunLoopRef指針,也就是當(dāng)前runloop
- 然后調(diào)用函數(shù)CFRunLoopObserverCreate(<#CFAllocatorRef allocator#>, <#CFOptionFlags activities#>, <#Boolean repeats#>, <#CFIndex order#>, <#CFRunLoopObserverCallBack callout#>, <#CFRunLoopObserverContext context#>)
CFAllocatorRef allocator直接使用NULL,
CFOptionFlags activities指的是前面說(shuō)到的可以監(jiān)聽的幾種狀態(tài),
Boolean repeats是否持續(xù)監(jiān)聽,
CFIndex order用0,
CFRunLoopObserverCallBack callout是回調(diào)函數(shù),需要一個(gè)C函數(shù)指針,函數(shù)的類型是typedef void (CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
CFRunLoopObserverContext *context是上下文,它的結(jié)構(gòu)是這樣的
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
} CFRunLoopObserverContext;
CFRunLoopObserverContext的info就是函數(shù)CFRunLoopObserverCallBack的參數(shù)info.
最后調(diào)用CFRunLoopAddObserver函數(shù)來(lái)執(zhí)行
創(chuàng)建一個(gè)Observer觀察者添加到主線程RunLoop的CommonMode模式下觀察呆细,創(chuàng)建一個(gè)持續(xù)的子線程專門用來(lái)監(jiān)控主線程的RunLoop狀態(tài)型宝,一旦發(fā)現(xiàn)進(jìn)入睡眠前的KCFRunLoopBeforeSource狀態(tài),或者喚醒后的狀態(tài)KCFRunLoopAfterWaiting絮爷,在設(shè)置的時(shí)間閾值內(nèi)一直沒有變化趴酣,即可判斷為卡頓,根據(jù)堆棧的信息坑夯,可以分析出是哪個(gè)方法比較耗時(shí).
三.Runloop與其他模塊相關(guān)
釋放池
程序啟動(dòng)后,主線程 RunLoop 里注冊(cè)了兩個(gè) Observer岖寞,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler(),一是,監(jiān)測(cè)Entry(即將進(jìn)入Loop)狀態(tài),其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池,其 order 是-2147483647柜蜈,優(yōu)先級(jí)最高慎璧,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前床嫌;另外一個(gè),Observer 監(jiān)視了兩個(gè)事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池胸私;Exit(即將退出Loop) 時(shí)調(diào)用 _objc_autoreleasePoolPop() 來(lái)釋放自動(dòng)釋放池厌处。這個(gè) Observer 的 order 是 2147483647,優(yōu)先級(jí)最低岁疼,保證其釋放池子發(fā)生在其他所有回調(diào)之后.UI事件
蘋果注冊(cè)了一個(gè) Source1 (基于 mach port 的) 用來(lái)接收系統(tǒng)事件阔涉,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()。
當(dāng)一個(gè)硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后捷绒,首先由 IOKit.framework 生成一個(gè) IOHIDEvent 事件并由 SpringBoard 接收瑰排。SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸暖侨,加速椭住,接近傳感器等幾種 Event,隨后用 mach port 轉(zhuǎn)發(fā)給需要的App進(jìn)程字逗。隨后蘋果注冊(cè)的那個(gè) Source1 就會(huì)觸發(fā)回調(diào)京郑,并調(diào)用 _UIApplicationHandleEventQueue() 進(jìn)行應(yīng)用內(nèi)部的分發(fā)。
_UIApplicationHandleEventQueue() 會(huì)把 IOHIDEvent 處理并包裝成 UIEvent 進(jìn)行處理或分發(fā)葫掉,其中包括識(shí)別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow 等些举。通常事件比如 UIButton 點(diǎn)擊、touchesBegin/Move/End/Cancel 事件都是在這個(gè)回調(diào)中完成的俭厚。手勢(shì)
_UIApplicationHandleEventQueue() 識(shí)別了一個(gè)手勢(shì)時(shí)户魏,其首先會(huì)調(diào)用 Cancel 將當(dāng)前的 touchesBegin/Move/End 系列回調(diào)打斷。隨后系統(tǒng)將對(duì)應(yīng)的 UIGestureRecognizer 標(biāo)記為待處理挪挤。蘋果注冊(cè)了一個(gè) Observer 監(jiān)測(cè) BeforeWaiting (Loop即將進(jìn)入休眠) 事件叼丑,這個(gè)Observer的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver(),其內(nèi)部會(huì)獲取所有剛被標(biāo)記為待處理的 GestureRecognizer扛门,并執(zhí)行GestureRecognizer的回調(diào)幢码。
當(dāng)有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時(shí),這個(gè)回調(diào)都會(huì)進(jìn)行相應(yīng)處理尖飞。GCD
當(dāng)調(diào)用 dispatch_async(dispatch_get_main_queue(), block) 時(shí)症副,libDispatch 會(huì)向主線程的 RunLoop 發(fā)送消息,RunLoop會(huì)被喚醒政基,并從消息中取得這個(gè) block贞铣,并在回調(diào)里執(zhí)行這個(gè) block。Runloop只處理主線程的block沮明,dispatch 到其他線程仍然是由 libDispatch 處理的辕坝。
- performSelector
除了基于端口的源,Cocoa定義了自定義輸入源荐健,允許你在任何線程執(zhí)行selector方法酱畅。和基于端口的源一樣琳袄,執(zhí)行selector請(qǐng)求會(huì)在目標(biāo)線程上序列化,減緩許多在線程上允許多個(gè)方法容易引起的同步問(wèn)題纺酸。不像基于端口的源窖逗,一個(gè)selector執(zhí)行完后會(huì)自動(dòng)從run loop里面移除。
當(dāng)在其他線程上面執(zhí)行selector時(shí)餐蔬,目標(biāo)線程須有一個(gè)活動(dòng)的run loop碎紊。對(duì)于你創(chuàng)建的線程,這意味著線程在你顯式的啟動(dòng)run loop之前是不會(huì)執(zhí)行selector方法的樊诺,而是一直處于休眠狀態(tài)仗考。