二話不說先上我麥來壓壓驚
什么是RunLoop辖佣?
RunLoop顧名思義運行著的循環(huán)霹抛,而且是一個死循環(huán),本質(zhì)是一個do,while循環(huán)卷谈,他負責監(jiān)聽幾乎所有的事件(觸摸事件杯拐,網(wǎng)絡事件,時鐘事件)世蔗,當監(jiān)聽到了事件就會喚醒線程去執(zhí)行端逼,沒事件RunLoop的線程就休眠。
RunLoop常見的模式
RunLoop在同一時間中只能響應一種模式下的事件污淋,同時會關閉上一種模式顶滩;不同的事件都有對應的模式,處理事件都會切換到事件對應的模式寸爆。
<ul>
<li> NSDefalutRunLoopMode //默認模式
<li> UITrackingRunLoopMode //拖動事件诲祸,例如:UIScrollView
<li> NSRunLoopCommonModes //占位模式,包含前面兩個
RunLoop跟線程的關系
RunLoop跟線程是一一對應的而昨,不能主動創(chuàng)建救氯,只能通過獲取來拿到RunLoop對象
-
在主線程中RunLoop是默認創(chuàng)建開啟的,來保證線程不死歌憨,程序不退出着憨;在分線程中,默認沒有創(chuàng)建RunLoop务嫡,線程在執(zhí)行完任務之后就會退出甲抖,當我們在線程中獲取RunLoop時漆改,它才會創(chuàng)建,一旦run起來准谚,這個分線程將跟主線程一樣不會主動退出挫剑。
開啟一個線程,并創(chuàng)建一個定時器 NSThread *thread = [[NSThread alloc]initWithBlock:^{ NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"哈哈"); }]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; //1.[[NSRunLoop currentRunLoop] run]; //2.while (!_isFinished) { // [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]]; //} NSLog(@"你看,來了嗎柱衔?"); }]; [thread start];
來看看運行結(jié)果:
發(fā)現(xiàn)定時器并沒有執(zhí)行回調(diào)方法!
原因:子線程中默認是不開啟RunLoop的樊破,所以線程里面代碼執(zhí)行完之后,線程就銷毀了唆铐。
解決:讓線程常駐 -> 開啟RunLoop哲戚。直接run ->
[[NSRunLoop currentRunLoop] run];
但是在執(zhí)行run方法之后,在當前線程中后面的代碼將永遠不會再執(zhí)行艾岂,比如NSLog(@"你看顺少,來了嗎?");
王浴,因為RunLoop會一直在那里循環(huán)脆炎,所以這個線程將永遠不會再自動銷毀,只能通過執(zhí)行[NSThread exit]
來暴力退出氓辣,RunLoop也會隨之關閉秒裕,當然線程中后續(xù)代碼更加不會再執(zhí)行。-
手動添加一個循環(huán)來控制RunLoop循環(huán)筛婉,
runUntilDate:
同run方法開啟RunLoop簇爆,只不過是加了一個時間限制癞松,在未來多少秒暫停爽撒,這樣通過手動控制RunLoop循環(huán)就可以控制線程的生命周期,當關閉此循環(huán)响蓉,Runloop也會停止硕勿,然后會執(zhí)行線程剩余代碼,之后線程銷毀枫甲,這樣做的好處就是可控制源武。while (!_isFinished) { [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]]; }
RunLoop優(yōu)化
RunLoop如何優(yōu)化?我們都知道耗時操作放到子線程中執(zhí)行想幻,更新UI放到主線程執(zhí)行(在子線程中更新UI也會等到子線程所有操作執(zhí)行完了然后再到主線程中執(zhí)行UI操作粱栖,因為UIKit是線程不安全的:UI控件都是用的原子屬性->效率高。)脏毯,那么如果更新UI耗時長怎么辦闹究?其實是因為RunLoop在一次循環(huán)當中執(zhí)行了過多的復雜的UI操作,我們需要對UI操作做拆分食店,然后增加RunLoop的循環(huán)次數(shù)來分步執(zhí)行渣淤,一次循環(huán)只做一個UI更新模塊赏寇,從而保證RunLoop的流暢性。
- (void)addRunLoopObserver{
//添加RunLoop監(jiān)聽,需要使用CFRunLoop价认,因為NSRunloop沒有這個功能
//1.獲取runloop
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
//2.1創(chuàng)建上下文
CFRunLoopObserverContext context = {
0, //這個context的版本
(__bridge void *)(self), //傳入的參數(shù)嗅定,這里我傳入控制器
CFRetain, //告訴它retain是調(diào)用哪個函數(shù)
CFRelease, //告訴它release是調(diào)用哪個函數(shù)
nil,
};
//2.2創(chuàng)建runloop觀察者
/*
CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator,
CFOptionFlags activities,
Boolean repeats,
CFIndex order,
CFRunLoopObserverCallBack callout,
CFRunLoopObserverContext *context);
@param allocator:這個參數(shù)用來分配空間給新的對象。默認情況下使用NULL或者kCFAllocatorDefault用踩。
@param activities:設置Runloop的運行階段的標志渠退,當運行到此階段時,CFRunLoopObserver會被調(diào)用
@param repeats:CFRunLoopObserver是否循環(huán)調(diào)用捶箱,false為單詞調(diào)用智什,否則循環(huán)調(diào)用。
@param order:CFRunLoopObserver的優(yōu)先級丁屎,當在Runloop同一運行階段中有多個CFRunLoopObserver時荠锭,根據(jù)這個來先后調(diào)用CFRunLoopObserver。正常情況下使用0晨川。
@param callout:回調(diào)函數(shù)
@param context:CFRunLoopObserver結(jié)構(gòu)體里面的一個結(jié)構(gòu)體证九,它主要用來給回調(diào)函數(shù)傳遞消息的。
@return CFRunLoopObserverRef:觀察者指針對象
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(NULL,
kCFRunLoopBeforeWaiting,
YES, 0,
callBack,
&context);
//3.給runloop添加觀察者
CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes);
//4.通過定時器來增加runloop循環(huán)次數(shù)
[NSTimer scheduledTimerWithTimeInterval:0.001
target:self
selector:@selector(timerAction)
userInfo:nil
repeats:YES];
}
- (void)timerAction{
//不執(zhí)行任何代碼共虑,只作為一個觸發(fā)runloop監(jiān)聽回調(diào)的功能愧怜。
}
/**
回調(diào)函數(shù),在kCFRunLoopBeforeWaiting(runloop等待執(zhí)行循環(huán)之前)情況下調(diào)用,因為定時器給的是0.001秒妈拌,所以這里調(diào)用非常頻繁拥坛,保證一次循環(huán)執(zhí)行一個任務
@param observer 觀察者對象
@param activity Runloop的運行階段的標志
@param info CFRunLoopObserverContext傳入的參數(shù)
*/
void callBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
//1.拿到傳過來的參數(shù),再進行轉(zhuǎn)換,因為這是C函數(shù)尘分,不能直接調(diào)用self猜惋,所以在添加觀察的時候把self傳過來。
ViewController *vc = (__bridge ViewController *)(info);
//2.執(zhí)行拆分之后的UI模塊(拆分的模塊用task數(shù)組裝起來培愁,具體任務包裝在block中著摔,在這里只要執(zhí)行block即可)
if (vc.tasks.count == 0) {
return;
}
RunLoopBlock block = vc.tasks.firstObject;
!block?:block();
[vc.tasks removeObjectAtIndex:0];
}