RunLoop
從字面意思來(lái)看:跑圈、運(yùn)動(dòng)循環(huán)
基本用法:保持程序持續(xù)運(yùn)行、處理App中的各種事件(觸摸事件、定時(shí)器事件、SEL等等)
為什么需要它:節(jié)省CPU資源、 提高性能
如果沒(méi)有RunLoop,程序在執(zhí)行到7行就結(jié)束了。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
有Runloop后,程序就相當(dāng)于一直在做循環(huán)
在這個(gè)循環(huán)內(nèi)部不斷地處理各種任務(wù)(比如Source、Timer、Observer)
int main(int argc, const char * argv[]) {
BOOL running = YES;
do{
// 執(zhí)行各種任務(wù),處理各種時(shí)間
}while (running);
return 0;
}
程序中的Runloop----UIApplicationMain
int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
1.在UIApplicationMain函數(shù)內(nèi)部就啟動(dòng)了一個(gè)RunLoop
2.UIApplicationMain函數(shù)一直沒(méi)有返回,保持了程序的持續(xù)運(yùn)行
3.這個(gè)默認(rèn)啟動(dòng)的RunLoop是主線程關(guān)聯(lián)的。
4.一個(gè)線程對(duì)應(yīng)一個(gè)RunLoop,主線程的RunLoop默認(rèn)已經(jīng)啟動(dòng)
5.子線程的RunLoop得手動(dòng)啟動(dòng)(調(diào)用run方法)
6.RunLoop只能選擇一個(gè)Mode啟動(dòng),如果當(dāng)前Mode中沒(méi)有任Source、Timer、Observer,那么就直接退出RunLoop
RunLoop里面有兩套api用來(lái)訪問(wèn)和使用RunLoop
1、Foundation--NSRunLoop
2、Core Foundation --- CFRunloopRef
二者異同點(diǎn):
NSRunLoop和CFRunloopRef都代表RunLoop對(duì)象,NSRunLoop是對(duì)CFRunloopRef一層OC的封裝
RunLoop與線程:
每條線程都有一個(gè)RunLoop對(duì)象,主線程默認(rèn)已經(jīng)創(chuàng)建好了,子線程需要主動(dòng)創(chuàng)建
Runloop在第一次獲取時(shí)創(chuàng)建,在線程結(jié)束后銷毀。
/* 1. Foundation */
// 獲取當(dāng)前線程
NSRunLoop *roop = [NSRunLoop currentRunLoop];
// 獲取主線程
[NSRunLoop mainRunLoop];
/*2. Core Foundation */
// 獲取當(dāng)期線程
CFRunLoopGetCurrent();
// 獲取主線程
CFRunLoopGetMain();
RunLoop相關(guān)類(Runloop中如果沒(méi)有Source,Observre,Timer,Mode,就會(huì)結(jié)束)
// Runloop對(duì)象
CFRunLoopRef;
// Runloop事件源(數(shù)據(jù)源)
CFRunLoopSourceRef;
// Runloop觀察者
CFRunLoopObserverRef;
// Runloop時(shí)間源
CFRunLoopTimerRef;
// Runloop模式
CFRunloopModeRef;
Runloop里面相關(guān)類的互相關(guān)系-

// 獲取當(dāng)前Runloop的模式
NSString *runloopMode = [NSRunLoop currentRunLoop].currentMode;
1.同一時(shí)間只可以運(yùn)行其中的一種model
2.切換Model只能退出Runloop,重新進(jìn)入
3.系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode
//App默認(rèn)的Mode,通常主線程是在這個(gè)Mode下運(yùn)行
NSDefaultRunLoopMode:
//界面追蹤Mode,用于ScrollView追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他Mode影響
UITrackingRunLoopMode
//這是一個(gè)占位用的Mode,不是一種正真的Mode
NSRunLoopCommonModes
//在剛啟動(dòng)App進(jìn)入的第一個(gè)Mode,啟動(dòng)完成以后就不再使用
UIInitializationRunLoopMode
//接收系統(tǒng)事件的內(nèi)部Mode,通常用不到
GSEventReceiveRunLoopMode
CFRunLoopTimerRef
CFRunLoopTimerRef是基于時(shí)間的觸發(fā)器
CFRunLoopTimerRef基本上說(shuō)就是NSTimer,它受RunLoop的Mode影響
NSTimer *time = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
// 定時(shí)器只運(yùn)行在NSDefaultRunLoopMode下,一旦RunLoop進(jìn)入其他模式,這個(gè)定時(shí)器就不會(huì)工作
[[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode];
// 定時(shí)器會(huì)跑在標(biāo)記為common modes的模式下
// 標(biāo)記為common modes的模式:包含:UITrackingRunLoopMode和kCFRunLoopDefaultMode
[[NSRunLoop currentRunLoop] addTimer:time forMode:NSRunLoopCommonModes];
GCD不受RunLoop的Mode影響
// 獲取一個(gè)全局并發(fā)隊(duì)列
/*
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默認(rèn)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 1. 設(shè)置定時(shí)器
// dispatchQueue :決定了將來(lái)回調(diào)的方法在哪里執(zhí)行。
// dispatch_source_t timer 是一個(gè)OC對(duì)象
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 2.指定定時(shí)期開(kāi)始的時(shí)間和間隔的時(shí)間
// DISPATCH_TIME_NOW :第2個(gè)參數(shù):定時(shí)器開(kāi)始時(shí)間 (也可以抽出來(lái),設(shè)置間隔時(shí)間)
dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 1.0 *NSEC_PER_SEC);
// intervalInSeconds :第三個(gè)參數(shù):定時(shí)器開(kāi)始后的間隔時(shí)間
// leewayInSeconds:第四個(gè)參數(shù):間隔精準(zhǔn)度,0代標(biāo)最精準(zhǔn),傳入一個(gè)大于0的數(shù),代表多少秒的范圍是可以接收的,主要為了提高程序性能,積攢一定的時(shí)間,Runloop執(zhí)行完任務(wù)會(huì)睡覺(jué),這個(gè)方法讓他多睡一會(huì),積攢時(shí)間,任務(wù)也就相應(yīng)多了一點(diǎn),而后一起執(zhí)行
// 開(kāi)始時(shí)間
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
// 3.指定定時(shí)器回調(diào)方法
dispatch_source_set_event_handler(timer, ^{
NSLog(@"___________");
});
// 開(kāi)啟定時(shí)器
dispatch_resume(timer);
CFRunLoopSourceRef(事件源、輸入源)
Port-Based Sources (端口)
Custom Input (自定義事件)
Cocoa Perform Selector Sources
按照函數(shù)的調(diào)用棧
Source0:非基于Port的
Source1:基于Port 通過(guò)內(nèi)核和其他線程通信,接收分發(fā)系統(tǒng)事件
CFRunLoopObserverRef(觀察者)
能夠監(jiān)聽(tīng)RunLoop的狀態(tài)改變

CFRunLoopObserverRef
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 1
kCFRunLoopBeforeTimers = (1UL << 1), // 2
kCFRunLoopBeforeSources = (1UL << 2), // 4
kCFRunLoopBeforeWaiting = (1UL << 5), // 32
kCFRunLoopAfterWaiting = (1UL << 6), // 64
kCFRunLoopExit = (1UL << 7), // 128
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
創(chuàng)建一個(gè)Observer
/*
第1個(gè)參數(shù):如何給Observer分配存儲(chǔ)空間
第2個(gè)參數(shù):需要監(jiān)聽(tīng)的狀態(tài)類型/kCFRunLoopAllActivities監(jiān)聽(tīng)所有狀態(tài)
第3個(gè)參數(shù):是否每次需要監(jiān)聽(tīng)?
第4個(gè)參數(shù):優(yōu)先級(jí)(0)
第5個(gè)參數(shù):監(jiān)聽(tīng)狀態(tài)改變后的回調(diào)
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即將進(jìn)入Runloop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即將處理timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即將處理Source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即將睡眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"剛從休眠中喚醒");
break;
case kCFRunLoopExit:
NSLog(@"即將退出RunLoop");
break;
default:
break;
}
});
/*
第1個(gè)參數(shù):要給那個(gè)RunLoop添加觀察者
第2個(gè)參數(shù):添加的Observer對(duì)象
第3個(gè)參數(shù):在那種模式下監(jiān)聽(tīng)
*/
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
// 釋放對(duì)象
CFRelease(observer);
由于ARC只管理Foundation框架的內(nèi)容,所以我們?cè)贑ore Foundation 框架創(chuàng)建的對(duì)象必須手動(dòng)釋放。
規(guī)律:
凡是帶有copy、create、retain等字眼的函數(shù),創(chuàng)建出來(lái)的CF對(duì)象,都需要在最后做一次release
官方對(duì)于RunLoop的解釋:

RunLoop處理邏輯,整理:自動(dòng)釋放池的生命周期
RunLoop在進(jìn)入這個(gè) kCFRunLoopBeforeWaiting時(shí),會(huì)對(duì)自動(dòng)釋放池銷毀

Runloop:在開(kāi)發(fā)中有什么作用?
1.NSTimer
2.ImageView的顯示
3.PerformSelector
4.常駐線程
5.自動(dòng)釋放池
PerformSelector
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
/*
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSString *> *)modes;
*/
// 指定模式下進(jìn)行特定的操作
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"rightPic"] afterDelay:2.0 inModes:@[NSDefaultRunLoopMode]];
}
常駐線程:
默認(rèn)情況下,一個(gè)線程只能使用一次,也就是只能執(zhí)行一個(gè)操作
我們需要做得到就是強(qiáng)引用,保留線程,同時(shí)添加Source Or Timer,注:系統(tǒng)只會(huì)監(jiān)測(cè)Source Or Timer,不會(huì)檢查Observer
1.子線程的Runloop需要手動(dòng)創(chuàng)建
2.子線程的Runloop需要手動(dòng)開(kāi)啟
3.如果子線程的NSRunLoop沒(méi)有設(shè)置Source Or Timer 那么子線程會(huì)立刻關(guān)閉。
- (void)ViewDidLoad
{
[super viewDidLoad];
SYThread *thread = [[SYThread alloc] initWithTarget:self selector:@selector(show) object:nil];
self.thread = thread;
[thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:@"時(shí)間計(jì)算" waitUntilDone:YES modes:@[NSRunLoopCommonModes]];
}
- (void)show
{
NSLog(@"%s", __func__);
// 這句話并沒(méi)有意義,只是保證線程不死
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
// 上面線程不死,這句話永遠(yuǎn)不會(huì)打印
NSLog(@"-------$$$$");
}
- (void)test
{
NSLog(@"11111");
}