iOS開(kāi)發(fā)之RunLoop
什么是RunLoop
- 運(yùn)行循環(huán),跑圈
- 其實(shí)內(nèi)部就是do-while循環(huán),在這個(gè)循環(huán)n內(nèi)部不斷的處理各種任務(wù)(比如Source/Timer/Observer)
- 一個(gè)線程對(duì)應(yīng)一個(gè)RunLoop,主線程的RunLoop默認(rèn)啟動(dòng)了,子線程需要手動(dòng)調(diào)用(run方法)
- RunLoop只能選擇一個(gè)Mode啟動(dòng),如果當(dāng)前Mode中沒(méi)有任何Source/Timer/Observer,那就直接退出RunLoop
RunLoop的作用
- 讓程序一直運(yùn)行并接受用戶輸入
- 決定程序在何時(shí)應(yīng)該處理哪些Event
- 調(diào)用結(jié)構(gòu)(Message Queue)
- 節(jié)省CPU時(shí)間
RunLoop對(duì)象
- Foundation :
NSRunLoop(OC對(duì)C的RunLoop的簡(jiǎn)單的封裝)
[NSRunLoop currentRunLoop]; //獲取當(dāng)前線程的Runloop
[NSRunLoop mainRunLoop]; //獲取主線程的RunLoop
- Core Foundation
CFRunLoop(C語(yǔ)言 開(kāi)源 跨平臺(tái)的)
CFRunLoopGetCurrent();//獲取當(dāng)前線程的Runloop
CFRunLoopGetMain(); //獲取主線程的RunLoop
RunLoop的機(jī)制
- 每條線程都有唯一一個(gè)與之對(duì)應(yīng)的Runloop對(duì)象
- 主線程的RunLoop已經(jīng)創(chuàng)建好了,子線程的Runloop需要自動(dòng)創(chuàng)建
- RunLoop在第一次獲取時(shí)創(chuàng)建蔬捷,在線程結(jié)束時(shí)銷毀
CFRunLoopModelRef
代表的是Runloop的運(yùn)行模式
- 一個(gè)Runloop包含若干個(gè)Mode,每個(gè)Model又包含了多個(gè)Source/Timer/Observer
- Runloop在同一段時(shí)間只能且必須在一種特定的Mode下Run
- 更換Mode時(shí),需要停止當(dāng)前的Loop,然后重啟新的Loop
- Mode是iOS App滑動(dòng)順利的關(guān)鍵
- 可以自己定制Mode(基本不會(huì)發(fā)生的)
NSDefaultRunLoopMode 默認(rèn)狀態(tài),空閑狀態(tài),通常主線程是在這個(gè)Mode下運(yùn)行
UITrackingRunLoopMode 界面跟蹤 Mode, 滑動(dòng)ScrollView時(shí)
UIInitializationRunLoopMode 私有,App啟動(dòng)時(shí)進(jìn)入的第一個(gè)Mode 啟動(dòng)完后不在使用
NSRunLoopCommonModes ? Mode集合 默認(rèn)包括上面第一和第二
GSEventReceiveRunLoopMode 接受系統(tǒng)內(nèi)部時(shí)間的Mode
CFRunLoopSource
- Source是Runloop的數(shù)據(jù)源的抽象類(類似IOS中的protocol)
- 定義了2個(gè)版本的Source
- Source0 :處理app的內(nèi)部時(shí)間,App自己負(fù)責(zé)管理 如:UIEvent CFSocket
- Source1 :用Runloop和內(nèi)核管理,Mach port驅(qū)動(dòng),如 CFMachPort CFMessagePort (Port可以用于進(jìn)程間的端口通訊)
CFRunLoopTimerRef
- CFRunLoopTimerRef是基于時(shí)間的觸發(fā)器
- 基本上說(shuō)的就是NSTimer
CFRunLoopObserver
向外部報(bào)告Runloop當(dāng)前狀態(tài)的更改,能夠監(jiān)聽(tīng)Runloop的狀態(tài)的改變
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即將進(jìn)入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
kCFRunLoopBeforeWaiting = (1UL << 5),// 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), //退出
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
// 創(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);
注意這里面的observer是需要釋放的: 在ARC中自動(dòng)內(nèi)存管理的是OC的對(duì)象
CF的內(nèi)存管理(Core Foundation)
- 凡是帶有Create马篮、Copy空凸、Retain等字眼的函數(shù)喉脖,創(chuàng)建出來(lái)的對(duì)象殿如,都需要在最后做一次release 比如CFRunLoopObserverCreate.
- release函數(shù):CFRelease(對(duì)象);
RunLoop的處理邏輯
RunLoopObserver與Autorelease Pool
在RunLoop睡覺(jué)之前釋放(kCFRunLoopBeforeWaiting)
UITrackingRunLoopMode 與 Timer
Timer默認(rèn)是被添加在NSDefaultRunLoopMode中的,當(dāng)ScrollerView滑動(dòng)的時(shí)候就會(huì)影響到
Timer,若不希望Timer被影響,需要添加到NSRunLoopCommonModes
[[NSRunLoop currentRunLoop] addTimer:[NSTimer timerWithTimeInterval:1 target:self selector:@selector(timeaction) userInfo:nil repeats:NO] forMode:NSRunLoopCommonModes];
Runloop與dispath_get_main_queue()
GCD到dispath到main queque的block被分發(fā)到main Runloop執(zhí)行
GCD中的定時(shí)器和Runloop沒(méi)有關(guān)系的,GCD的定時(shí)器是不受RunLoop的Mode的影響的
// 獲得隊(duì)列
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue = dispatch_get_main_queue();
// 創(chuàng)建一個(gè)定時(shí)器(dispatch_source_t本質(zhì)還是個(gè)OC對(duì)象)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 設(shè)置定時(shí)器的各種屬性(什么時(shí)候開(kāi)始任務(wù)叉弦,每隔多長(zhǎng)時(shí)間執(zhí)行一次)
// GCD的時(shí)間參數(shù)摹恨,一般是納秒(1秒 == 10的9次方納秒)
// 何時(shí)開(kāi)始執(zhí)行第一個(gè)任務(wù)
// dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC) 比當(dāng)前時(shí)間晚3秒
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
dispatch_source_set_timer(self.timer, start, interval, 0);
// 設(shè)置回調(diào)
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"------------%@", [NSThread currentThread]);
count++;
// if (count == 4) {
// // 取消定時(shí)器
// dispatch_cancel(self.timer);
// self.timer = nil;
// }
});
// 啟動(dòng)定時(shí)器
dispatch_resume(self.timer);
RunLoop的掛起與喚醒
- 指定用于喚醒的mach_port端口
- 調(diào)用mach_msg監(jiān)聽(tīng)喚醒端口,被喚醒前,系統(tǒng)內(nèi)核將這個(gè)線程掛起,停留在mach_msg_trap狀態(tài)
- 由另一個(gè)線程(或者另一個(gè)進(jìn)程中的線程) 向內(nèi)核發(fā)送這個(gè)端口的msg后,trap狀態(tài)被喚醒.
//在子線程中默認(rèn)是沒(méi)有RunLoop的
//獲取Runloop,當(dāng)當(dāng)前線程沒(méi)有runloop的時(shí)候,該方法就會(huì)啟動(dòng)runloop
NSRunLoop *loop = [NSRunLoop currentRunLoop];
//給Runloop一個(gè)端口,這樣就可以保持Runloop處于喚醒的狀態(tài)
[loop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
//run
[loop run];
RunLoop創(chuàng)建一個(gè)常駐服務(wù)線程的方法
[[NSThread currentThread] setName:@"thread1"];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefalutRunLoopMode]//一直活著
[runLoop run];
Topic: 一個(gè)TableView延遲加載圖片的思路
UIImageView * iconImageView = [UIImageView new];
UIImage *image = nil;
[iconImageView performSelector:@selector(setImage:) withObject:image afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
//NSDefaultRunLoopMode 設(shè)置為這個(gè)模式的時(shí)候 當(dāng)TableView在滑動(dòng)到時(shí)候Runloop在UITrackingRunLoopMode模式,這樣setimage方法就不會(huì)被調(diào)用.只有不滑動(dòng)的時(shí)候,runloop切換到NSDefaultRunLoopMode模式,這時(shí)候設(shè)置圖片
應(yīng)用場(chǎng)景
- 開(kāi)啟一個(gè)常駐線程(讓一個(gè)子線程不進(jìn)入消亡狀態(tài),等待其他線程發(fā)送消息,處理其他時(shí)間)
- 在子線程中開(kāi)啟一個(gè)定時(shí)器
- 在子線程中長(zhǎng)期監(jiān)控行為
- 可以控制定時(shí)器在哪種模式下運(yùn)行
- 可以讓某些任務(wù)在特定模式下執(zhí)行
- 可以添加Observer監(jiān)聽(tīng)Runloop的狀態(tài),比如監(jiān)聽(tīng)點(diǎn)擊時(shí)間前做一些事情