什么是RunLoop
從字面意思看
運(yùn)行循環(huán)
跑圈
基本作用
保持程序的持續(xù)運(yùn)行
處理App中的各種事件(比如觸摸事件、定時(shí)器事件、Selector事件)
節(jié)省CPU資源,提高程序性能:該做事時(shí)做事,該休息時(shí)休息
main函數(shù)中的RunLoop
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
-
UIApplicationMain函數(shù)內(nèi)部就啟動(dòng)了一個(gè)RunLoop - 所以
UIApplicationMain函數(shù)一直沒(méi)有返回,保持了程序的持續(xù)運(yùn)行 - 這個(gè)默認(rèn)啟動(dòng)的
RunLoop是跟主線程相關(guān)聯(lián)的
RunLoop對(duì)象
iOS中有2套API來(lái)訪問(wèn)和使用
RunLoop-
FoundationNSRunLoop
-
Core FoundationCFRunLoopRef
NSRunLoop和CFRunLoopRef都代表著RunLoop對(duì)象NSRunLoop是基于CFRunLoopRef的一層OC包裝,所以要了解RunLoop內(nèi)部結(jié)構(gòu),需要多研究CFRunLoopRef層面的API(Core Foundation層面)
RunLoop與線程
每條線程都有唯一的一個(gè)與之對(duì)應(yīng)的
RunLoop對(duì)象主線程的
RunLoop已經(jīng)自動(dòng)創(chuàng)建好了,子線程的RunLoop需要主動(dòng)創(chuàng)建RunLoop在第一次獲取時(shí)創(chuàng)建,在線程結(jié)束時(shí)銷毀
獲得RunLoop對(duì)象
Foundation
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對(duì)象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對(duì)象
Core Foundation
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對(duì)象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對(duì)象
RunLoop相關(guān)類
-
Core Foundation中關(guān)于RunLoop的5個(gè)類 CFRunLoopRefCFRunLoopModeRefCFRunLoopSourceRefCFRunLoopTimerRefCFRunLoopObserverRef

RunLoop.png
CFRunLoopModeRef
-
CFRunLoopModeRef代表RunLoop的運(yùn)行模式 - 一個(gè)
RunLoop包含若干個(gè)Mode,每個(gè)Mode又包含若干個(gè)Source/Timer/Observer - 每次
RunLoop啟動(dòng)時(shí),只能指定其中一個(gè)Mode,這個(gè)Mode被稱作CurrentMode - 如果需要切換
Mode,只能退出Loop,再重新指定一個(gè)Mode進(jìn)入 - 這樣做主要是為了分隔開(kāi)不同組的
Source/Timer/Observer,讓其互不影響 - 系統(tǒng)默認(rèn)注冊(cè)了5個(gè)
Mode: -
kCFRunLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行 -
UITrackingRunLoopMode:界面跟蹤Mode,用于ScrollView追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他Mode影響 -
UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè)Mode,啟動(dòng)完成后就不再使用 -
GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部Mode,通常用不到 -
kCFRunLoopCommonModes: 這是一個(gè)占位用的Mode,不是一種真正的Mode
CFRunLoopSourceRef
-
CFRunLoopSourceRef是事件源(輸入源) - 以前的分法
Port-Based SourcesCustom Input SourcesCocoa Perform Selector Sources- 現(xiàn)在的分法
-
Source0:非基于Port的 -
Source1:基于Port的
CFRunLoopTimerRef
-
CFRunLoopTimerRef是基于時(shí)間的觸發(fā)器 - 基本上說(shuō)的就是
NSTimer
CFRunLoopObserverRef
-
CFRunLoopObserverRef是觀察者,能夠監(jiān)聽(tīng)RunLoop的狀態(tài)改變 - 可以監(jiān)聽(tīng)的時(shí)間點(diǎn)有以下幾個(gè)
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),//即將進(jìn)入Loop
kCFRunLoopBeforeTimers = (1UL << 1),//即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2),//即將處理Source
kCFRunLoopBeforeWaiting = (1UL << 5),//即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7),//即將推出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
RunLoop處理邏輯-官方版
- RunLoop官方處理邏輯圖.png
- RunLoop官方處理邏輯.png
RunLoop處理邏輯-網(wǎng)友整理版
- RunLoop處理邏輯-網(wǎng)友整理版.png
RunLoop應(yīng)用
-
NSTimer定時(shí)器 - 通常你會(huì)發(fā)現(xiàn)
NSTimer在用戶執(zhí)行一些操作時(shí)會(huì)暫停觸發(fā),這就是跟runloop有關(guān)系的
-(void)timer
{
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 定時(shí)器只運(yùn)行在NSDefaultRunLoopMode下,一旦RunLoop進(jìn)入其他模式,這個(gè)定時(shí)器就不會(huì)工作
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 定時(shí)器只運(yùn)行在UITrackingRunLoopMode下,一旦RunLoop進(jìn)入其他模式,這個(gè)定時(shí)器就不會(huì)工作
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// 定時(shí)器會(huì)跑在標(biāo)記為common modes的模式下
// 標(biāo)記為common modes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
-(void)timer2
{
// 調(diào)用了scheduledTimer返回的定時(shí)器,已經(jīng)自動(dòng)被添加到當(dāng)前runLoop中,而且是NSDefaultRunLoopMode
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 修改模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- 在子線程開(kāi)啟一個(gè)定時(shí)器
-(void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(execute) object:nil];
[self.thread start];
}
-(void)execute
{
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
-
observer監(jiān)聽(tīng)消息循環(huán)機(jī)制(觀察者)
/*
CF的內(nèi)存管理(Core Foundation)
1.凡是帶有Create、Copy、Retain等字眼的函數(shù),創(chuàng)建出來(lái)的對(duì)象,都需要在最后做一次release
* 比如CFRunLoopObserverCreate
2.release函數(shù):CFRelease(對(duì)象);
*/
-(void)observer
{
// 創(chuàng)建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"----監(jiān)聽(tīng)到RunLoop狀態(tài)發(fā)生改變---%zd", activity);
});
// 添加觀察者:監(jiān)聽(tīng)RunLoop的狀態(tài)
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 釋放Observer
CFRelease(observer);
}
-
PerformSelector使用和ImageView顯示 - 有時(shí)候?yàn)榱藘?yōu)化性能,在用戶滑動(dòng)交互時(shí)暫停圖片的加載,使得界面滑動(dòng)更加流暢
// 只在NSDefaultRunLoopMode模式下顯示圖片
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
- 常駐線程
- 蘋(píng)果幫我們管理線程時(shí),方法結(jié)束(下面的
run方法)線程就會(huì)被殺死,即使被@property (nonatomic, strong) NSThread *thread;全局變量引用,run方法結(jié)束時(shí)內(nèi)存中雖然還有這個(gè)線程,但是該線程也已經(jīng)處于了消亡狀態(tài),RunLoop可以幫我們使一個(gè)線程不會(huì)執(zhí)行完run方法,這樣該線程就不會(huì)消亡。 - 創(chuàng)建一個(gè)線程
//創(chuàng)建一個(gè)線程并開(kāi)啟
-(void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[self.thread start];
}
- 線程執(zhí)行的方法,
[[NSRunLoop currentRunLoop] run];可以自動(dòng)創(chuàng)建一個(gè)RunLoop,并將其開(kāi)啟,但是請(qǐng)注意,這樣的RunLoop中的source timer observer都為NULL這樣的RunLoop會(huì)自動(dòng)退出,并不能實(shí)現(xiàn)線程不死的效果,所以我們通過(guò)這個(gè)方法[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];隨便給RunLoop添加一個(gè)source。這樣就開(kāi)啟了一個(gè)RunLoop,下面這個(gè)run方法永遠(yuǎn)不會(huì)執(zhí)行完,所以第二次打印NSLog(@"---------");永遠(yuǎn)不會(huì)執(zhí)行
//線程執(zhí)行的方法
-(void)run
{
//打印當(dāng)前線程
NSLog(@"----------run----%@", [NSThread currentThread]);
//子線程需要手動(dòng)創(chuàng)建runloop
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"---------");
// [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
// [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
}
- 測(cè)試該線程是否死掉,點(diǎn)擊屏幕,觸發(fā)子線程執(zhí)行
test方法
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
-(void)test
{
NSLog(@"----------test----%@", [NSThread currentThread]);
}
- 自動(dòng)釋放池
@autoreleasepool {}將一些對(duì)象放入自動(dòng)釋放池,在runloop睡覺(jué)之前CFRunLoopBeforeWaiting會(huì)清理一次,在runloop喚醒時(shí)kCFRunLoopAfterWaiting會(huì)創(chuàng)建自動(dòng)釋放池


