文藝求關(guān)注.png
- <h5>初識RunLoop</h5>
- 從字面意義看:運(yùn)行循環(huán)
- <b>基本作用</b>
- 保持程序的運(yùn)行循環(huán)
- 處理app中的各種時間(比如觸摸事件、定時器事件、selector事件)
- 節(jié)省CPU資源,提高程序性能:該做事時做事,該休息時休息
- . . . . . . .
- <b>main函數(shù)中的runLoop</b>
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- UIApplicationMain函數(shù)內(nèi)部就啟動了一個RunLoop
- 所以UIApplication函數(shù)一直沒有返回,保持了程序的持續(xù)運(yùn)行
- 這個默認(rèn)啟動的RunLoop是跟主線程相關(guān)聯(lián)的
- <h5>RunLoop對象</h5>
- iOS中有2套API來訪問和使用RunLoop
- Foundation
- NSRunLoop - Core Foundation
- CFRunLoopRef
- Foundation
- NSRunLoop和CFRunLoopRef都代表著RunLoop對象
- NSRunLoop是基于CFRunLoopRef的一層OC包裝,所以要了解RunLoop內(nèi)部結(jié)構(gòu),需要多研究CFRunLoopRef層面的API(Core Foundation層面)
- iOS中有2套API來訪問和使用RunLoop
- <h5>RunLoop與線程間關(guān)系</h5>
- 每條線程都有唯一的一個與之對應(yīng)的RunLoop對象
- 主線程的RunLoop已經(jīng)自動創(chuàng)建好了,子線程的RunLoop需要主動創(chuàng)建
- RunLoop在第一次獲取時創(chuàng)建,在線程結(jié)束時銷毀
- <h5>獲得RunLoop對象的方法</h5>
- Foundation
[NSRunLoop currentRunLoop]; //獲得當(dāng)前線程的RunLoop對象
[NSRunLoop mainRunLoop]; //獲得主線程的RunLoop對象 - Core Foundation
CFRunLoopGetCurrent(); //獲得當(dāng)前線程的RunLoop對象
CFRunLoopGetMain(); //獲得主線程的RunLoop對象 - 如何轉(zhuǎn)化如上得到的兩個RunLoop函數(shù),使其地址一樣
mainRunLoop.getCFRunLoop
- Foundation
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
}
- (void) run {
// 如何創(chuàng)建子線程對應(yīng)的RunLoop,比較奇葩
// currentRunLoop本身是懶加載,會在第一次調(diào)用的時候判斷是否存在,如不存在,自動創(chuàng)建
NSLog(@"%@", [NSRunLoop currentRunLoop]);
NSLog(@"run ---------- %@", [NSThread currentThread]);
}
- <h5>RunLoop的相關(guān)類</h5>
- Core Foundation中關(guān)于RunLoop的5個類
CFRunLoopRef
CFRunLoopModeRef //RunLoop的運(yùn)行模式
CFRunLoopSourceRef // 數(shù)據(jù)源、事件源
CFRunLoopTimerRef //NSTimer
CFRunLoopObserverRef // 觀察、監(jiān)聽RunLoop
- Core Foundation中關(guān)于RunLoop的5個類
RunLoop5各類對應(yīng)關(guān)系.png
<b>* 注意點(diǎn):</b>
- 在RunLoop中有多個運(yùn)行模式,但是RunLoop只能選擇一種模式運(yùn)行
- mode里面至少要有一個timer或者source,observer可有可無(只是用來觀察/監(jiān)聽RunLoop)
- <b>CFRunLoopModeRef</b>
- <b>CFRunLoopModeRef代表RunLoop的運(yùn)行模式</b>
- 一個RunLoop包含若干個Mode,每個Mode又包含若干個Source/Timer/Observer
- 每次RunLoop啟動時,只能指定其中一個Mode,這個Mode被稱作CurrentMode
- 這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響
- <b>系統(tǒng)默認(rèn)注冊了5個Mode</b>
-
kCFRunLoopDefaultMode
:App的默認(rèn)Mode,通常主線程是在這個Mode下運(yùn)行(常用) -
UITrackingRunLoopMode
: 界面追蹤Mode,用于tableView、ScrollView追蹤觸摸滑動,保證界面滑動時不受其他Mode影響(常用) - UIInitializationRunLoopMode:在剛啟動App時,進(jìn)入的第一個mode,啟動后就不再使用
- GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部Mode,通常用不到
- kCFRunLoopCommonModes:這是一個占位用的Mode,不是真正的Mode
-
- <b>CFRunLoopModeRef代表RunLoop的運(yùn)行模式</b>
- <h5> CFRunLoopModeRef使用</h5>
- (void)timer {
// 1.創(chuàng)建定時器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
/**
2.添加定時器到RunLoop中,指定runLoop的運(yùn)行模式
@param NSTimer 定時器
@param Mode runLoop的運(yùn)行模式
*/
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//UITrackingRunLoopMode: 界面追蹤
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode
//占用,標(biāo)簽,凡是添加到NSRunLoopCommonModes中的事件都會被同時添加到打上common的運(yùn)行模式上
/**
0 : <CFString 0x10af41270 [0x10a0457b0]>{contents =
"UITrackingRunLoopMode"}
2 : <CFString 0x10a065b60 [0x10a0457b0]>{contents = "kCFRunLoopDefaultMode"}
*/
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void) run {
NSLog(@"run -----------%@------%@", [NSThread currentThread], [NSRunLoop currentRunLoop]);
}
- <b>子線程中使用RunLoop的注意點(diǎn)</b>
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[NSThread detachNewThreadSelector:@selector(timer) toTarget:self withObject:nil];
}
- (void)timer {
// 如果是在子線程中使用此timer創(chuàng)建方式,則需要手動創(chuàng)建runLoop
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
// 該方法內(nèi)部自動添加到runLoop中,并且設(shè)置運(yùn)行模式為默認(rèn)
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 子線程中創(chuàng)建runLoop之后不會執(zhí)行,需要手動開啟runLoop
[currentRunLoop run];
}
- <h5>定時器補(bǔ)充—不受RunLoop影響的定時器(GCD定時器)</h5>
@property (nonatomic, strong) dispatch_source_t timer;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
/**
01.創(chuàng)建GCD中的定時器
@param DISPATCH_SOURCE_TYPE_TIMER source的類型,表示是定時器
@param 0 描述信息,線程ID
@param 0 更詳細(xì)的描述信息
@param dispatchQueue 隊(duì)列,確定GCD定時器中的任務(wù)在哪個線程中執(zhí)行
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
/**
02.設(shè)置定時器(起始時間|間隔時間|精準(zhǔn)度)
@param timer 定時器對象
@param DISPATCH_TIME_NOW 起始時間,DISPATCH_TIME_NOW 從現(xiàn)在開始計(jì)時
@param intervalInSeconds * NSEC_PER_SEC 間隔時間
@param leewayInSeconds * NSEC_PER_SEC 精準(zhǔn)度,絕對精準(zhǔn)0
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
// 03.設(shè)置定時器執(zhí)行的任務(wù)
dispatch_source_set_event_handler(timer, ^{
NSLog(@"------------%@", [NSThread currentThread]);
});
// 04.啟動執(zhí)行
dispatch_resume(timer);
self.timer = timer;
}
- <b>注意點(diǎn):</b>
- 1.GCD的定時器與NSTimer的定時器相比,GCD的定時器時間更為精準(zhǔn)
- 2.GCD的定時器完全不受RunLoop Mode的影響
- <h5>CFRunLoopSourceRef</h5>
CFRunLoopSourceRef是事件源(輸入源)
-
以前的分法
- Port-Based Sources //基于端口的事件
- Custom Input Sources //自定義事件
- Cocoa Perform Selector Sources //perform Selector
-
現(xiàn)在的分發(fā):(通過函數(shù)調(diào)用站的方式來分)
- Source0:非基于Port的 //用戶觸發(fā)的事件
- Source1:基于Port的 //系統(tǒng)內(nèi)部的消息事件
- <h5>CFRunLoopObserverRef</h5>
- (void) observer {
/**
// 01.創(chuàng)建監(jiān)聽者
@param allocator 制定如何分配存儲空間
@param activities 要監(jiān)聽的狀態(tài) kCFRunLoopAllActivities 所有狀態(tài)
@param repeats 是否持續(xù)監(jiān)聽
@param order 優(yōu)先級,總是傳0
@param observer activity 當(dāng)狀態(tài)改變的時候回調(diào)
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
/**
kCFRunLoopEntry = (1UL << 0), //即將進(jìn)入runLoop
kCFRunLoopBeforeTimers = (1UL << 1), //即將處理timer事件
kCFRunLoopBeforeSources = (1UL << 2), //即將處理sources事件
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進(jìn)入睡眠
kCFRunLoopAfterWaiting = (1UL << 6), //被喚醒
kCFRunLoopExit = (1UL << 7), //runloop推出
kCFRunLoopAllActivities = 0x0FFFFFFFU //所有狀態(tài)
*/
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即將進(jìn)入runLoop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即將處理timer事件");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即將處理sources事件");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即將進(jìn)入睡眠");
break;
case kCFRunLoopExit:
NSLog(@"runloop推出" );
break;
case kCFRunLoopAllActivities:
NSLog(@"所有狀態(tài)");
break;
default:
break;
}
});
}
-
<h5>RunLoop處理邏輯—官方版</h5>
RunLoop處理邏輯-官方版.png - <b>RunLoop的事件隊(duì)列</b>
每次運(yùn)行run loop,你的線程的run loop會自動處理之前未處理的消息,并通知相關(guān)的觀察者。具體順序如下:- 1.通知觀察者run loop已經(jīng)啟動
- 2.通知觀察者任何即將要開始的定時器
- 3.通知貫徹著任何即將啟動的非基于端口的源
- 4.啟動任何準(zhǔn)備好的非基于端口的源
- 5.如果基于端口的源準(zhǔn)備好并處于等待狀態(tài),立即啟動;并進(jìn)入步驟9
- 6.通知觀察者線程進(jìn)入休眠
- 7.將線程置于休眠直到任一下面的事件發(fā)生:
- 某一事件到達(dá)基本端口的源
- 定時器啟動
- RunLoop設(shè)置的事件已經(jīng)超時
- RunLoop被顯示喚醒
- 8.通知觀察者線程被喚醒
- 9.處理為處理的事件
- 如果用戶定義的定時器啟動,處理定時器事件并重啟run loop。進(jìn)入步驟2
- 如果輸入源啟動,傳遞相應(yīng)的消息
- 如果run loop被顯示喚醒而且時間還未超時,重啟run loop。進(jìn)入步驟2
- 10.通知觀察者run loop結(jié)束
-
<h5>RunLoop處理邏輯—網(wǎng)友整理版</h5>
RunLoop處理邏輯-網(wǎng)友整理版.png
- <h5>RunLoop應(yīng)用—常駐線程</h5>
RunLoop在開發(fā)中有很多應(yīng)用場景,比如說:處理NSTimer不工作,ImageView的顯示,PerformSelector事件,常駐線程,以及自動釋放池等 - <b>常駐線程</b>
@property (nonatomic, strong) NSThread *thread;
// 在storyboard中有兩個按鈕,創(chuàng)建線程、讓線程繼續(xù)執(zhí)行任務(wù)
//需求:如何保證子線程不掛掉,同時執(zhí)行其他任務(wù)
- (IBAction)creatThreadBtnClick:(id)sender {
// 01.創(chuàng)建線程
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(task1) object:nil];
[self.thread start];
}
- (void)task1 {
NSLog(@"task1-----------%@", [NSThread currentThread]);
// 解決方法(保證子線程不掛掉,同時執(zhí)行其他任務(wù)):RunLoop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
//保證RunLoop不退出
// 方法一:添加Timer
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runOperation) userInfo:nil repeats:YES];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
// 方法二:添加Source
[runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]; // 給runLoop設(shè)置時間,10秒鐘后runLoop自動退出,(該設(shè)置只針對于子線程中的runLoop有用,對主線程中的runLoop沒有任何作用)
NSLog(@"--------end---------"); // 10秒鐘之后打印該內(nèi)容
}
- (void)runOperation {
NSLog(@"%s", __func__);
}
- (IBAction)otherOperationBtnClick:(id)sender {
[self performSelector:@selector(task2) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)task2 {
NSLog(@"task2-----------%@", [NSThread currentThread]);
}
- <h5>RunLoop常見面試題</h5>
- 什么是RunLoop?
- 從字面意思看:運(yùn)行循環(huán)、跑圈
- 其實(shí)它內(nèi)部就是do-while循環(huán),在這個循環(huán)內(nèi)部不斷地處理各種任務(wù)(比如Source、Timer、Observer)
- 一個線程對應(yīng)一個RunLoop,主線程的RunLoop默認(rèn)已經(jīng)啟動,子線程的RunLoop得手動啟動(調(diào)用run方法)
- RunLoop只能選擇一個Mode啟動,如果當(dāng)前Mode中沒有任何Source(Sources0、Sources1)、Timer,那么就直接退出RunLoop
- 自動釋放吃什么時候創(chuàng)建?什么時候釋放?
- 第一次創(chuàng)建在:啟動runLoop的時候
- 最后一次銷毀在:runLoop退出的時候
- 其他時候的創(chuàng)建和銷毀:當(dāng)runLoop即將睡眠的時候銷毀之前的釋放池,并重新創(chuàng)建一個新的
- 在開發(fā)中如何使用RunLoop?什么應(yīng)用場景?
- 開啟一個常駐線程(讓一個子線程不進(jìn)入消亡狀態(tài),等待其他線程發(fā)來消息,處理其他事件)
- 在子線程中開啟一個定時器
- 在子線程中進(jìn)行一些長期監(jiān)控
- 可以控制定時器在特定模式下執(zhí)行
- 可以讓某些事件(行為、任務(wù))在特定模式下執(zhí)行
- 可以添加Observer監(jiān)聽RunLoop的狀態(tài),比如監(jiān)聽點(diǎn)擊事件的處理(在所有點(diǎn)擊事件之前做一些事情)
- 開啟一個常駐線程(讓一個子線程不進(jìn)入消亡狀態(tài),等待其他線程發(fā)來消息,處理其他事件)
- 什么是RunLoop?
關(guān)注一下又不會懷孕.png