一讹剔、簡介
RunLoop
是一個對象,這個對象在循環(huán)中用來處理程序運行過程中出現(xiàn)的各種事件(比如說觸摸事件详民、UI刷新事件延欠、定時器事件、Selector事件)沈跨,從而保持程序的持續(xù)運行由捎;而且在沒有事件處理的時候,會進入睡眠模式饿凛,從而節(jié)省CPU資源狞玛,提高程序性能。
OSX/iOS 系統(tǒng)中笤喳,提供了兩個這樣的對象:NSRunLoop
和 CFRunLoopRef
为居。CFRunLoopRef
是在 CoreFoundation
框架內(nèi)的,它提供了純 C 函數(shù)的 API杀狡,所有這些 API 都是線程安全的蒙畴。NSRunLoop
是基于 CFRunLoopRef
的封裝,提供了面向?qū)ο蟮?API呜象,但是這些 API 不是線程安全的膳凝。
線程和 RunLoop 之間是一一對應(yīng)的,其關(guān)系是保存在一個全局的 Dictionary 里恭陡。線程剛創(chuàng)建時并沒有 RunLoop蹬音,如果你不主動獲取,那它一直都不會有休玩。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時著淆,RunLoop 的銷毀是發(fā)生在線程結(jié)束時。你只能在一個線程的內(nèi)部獲取其 RunLoop(主線程除外)拴疤。
二永部、RunLoop 內(nèi)部的邏輯
實際上 RunLoop 內(nèi)部是一個
do-while
循環(huán)。當(dāng)你調(diào)用CFRunLoopRun()
時呐矾,線程就會一直停留在這個循環(huán)里苔埋,直到超時或被手動停止
,該函數(shù)才會返回蜒犯。
三组橄、CFRunLoopRef之系統(tǒng)默認注冊的5個Mode
(1)kCFRunLoopDefaultMode
: App的默認 Mode荞膘,通常主線程是在這個 Mode 下運行的。
(2)UITrackingRunLoopMode
: 界面跟蹤 Mode玉工,用于 ScrollView 追蹤觸摸滑動羽资,保證界面滑動時不受其他 Mode 影響。
(3)UIInitializationRunLoopMode
: 在剛啟動 App 時第進入的第一個 Mode遵班,啟動完成后就不再使用削罩。
(4)GSEventReceiveRunLoopMode
: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到费奸。
(5)kCFRunLoopCommonModes
: 這是一個占位的 Mode,沒有實際作用进陡。
四愿阐、RunLoop的四個作用:
(1)使程序一直運行接受用戶輸入
(2)決定程序在何時應(yīng)該處理哪些Event
(3)調(diào)用解耦
(4)節(jié)省CPU時間
程序主線程一開始,就會一直跑趾疚,其內(nèi)部一定是開啟了一個和主線程對應(yīng)的RunLoop缨历,并且可以看出函數(shù)返回的是一個int返回值的 UIApplicationMain()函數(shù)
int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));
}
}
UIApplicationMain() 函數(shù),這個方法會為main thread 設(shè)置一個NSRunLoop 對象糙麦,使得我們的應(yīng)用可以在無人操作的時候休息辛孵,需要讓它干活的時候又能立馬響應(yīng)。
對其它線程來說赡磅,run loop默認是沒有啟動的魄缚,如果你需要更多的線程交互則可以手動配置和啟動,如果線程只是去執(zhí)行一個長時間的已確定的任務(wù)則不需要焚廊。在任何一個Cocoa程序的線程中冶匹,都可以通過:
NSRunLoop *runloop = [NSRunLoop currentRunLoop]; // 來獲取到當(dāng)前線程的run loop。
一個run loop就是一個事件處理循環(huán)咆瘟,用來不停的監(jiān)聽和處理輸入事件并將其分配到對應(yīng)的目標(biāo)上進行處理嚼隘。
NSRunLoop是一種更加高明的消息處理模式,他就高明在對消息處理過程進行了更好的抽象和封裝袒餐,這樣才能是的你不用處理一些很瑣碎很低層次的具體消息的處理飞蛹,在NSRunLoop中每一個消息就被打包在input source或者是timer source中了。使用run loop可以使你的線程在有工作的時候工作灸眼,沒有工作的時候休眠卧檐,這可以大大節(jié)省系統(tǒng)資源。
五幢炸、runloop應(yīng)用
1泄隔、 NSTimer
前面一直提到Timer Source作為事件源,事實上它的上層對應(yīng)就是NSTimer(其實就是CFRunloopTimerRef)這個開發(fā)者經(jīng)常用到的定時器(底層基于使用mk_timer實現(xiàn))宛徊,甚至很多開發(fā)者接觸RunLoop還是從NSTimer開始的佛嬉。其實NSTimer定時器的觸發(fā)正是基于RunLoop運行的逻澳,所以使用NSTimer之前必須注冊到RunLoop
,但是RunLoop為了節(jié)省資源并不會在非常準(zhǔn)確的時間點調(diào)用定時器暖呕,如果一個任務(wù)執(zhí)行時間較長斜做,那么當(dāng)錯過一個時間點后只能等到下一個時間點執(zhí)行,并不會延后執(zhí)行(NSTimer提供了一個tolerance屬性用于設(shè)置寬容度湾揽,如果確實想要使用NSTimer并且希望盡可能的準(zhǔn)確瓤逼,則可以設(shè)置此屬性)。
NSTimer的創(chuàng)建通常有兩種方式库物,盡管都是類方法霸旗,一種是timerWithTimeInterval
,另一種scheduledTimerWithTimeInterval
戚揭。二者最大的區(qū)別就是scheduledTimerWithTimeInterval
除了創(chuàng)建一個定時器外還會自動以NSDefaultRunLoopModeMode
添加到當(dāng)前線程RunLoop中诱告,不添加到RunLoop中的NSTimer
是無法正常工作的。
例如下面的代碼中如果timer2不加入到RunLoop
中是無法正常工作的民晒。同時注意如果滾動UIScrollView(UITableView/UICollectionview)
二者是無法正常工作的精居,但是如果將NSDefaultRunLoopMode改為NSRunLoopCommonModes則可以正常工作,這也解釋了前面介紹的Mode內(nèi)容潜必。
-(NSTimer *)timer1{
if(_timer1 == nil){
_timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timeInterval:) userInfo:nil repeats:YES];
}
return _timer1;
}
-(NSTimer *)timer2{
if(_timer2 == nil){
_timer2 = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(timeInterval:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:_timer2 forMode:NSDefaultRunLoopMode];
}
return _timer2;
}
問題一靴姿、target無法釋放導(dǎo)致內(nèi)存泄漏
定時器添加到runloop中后,runloop會將當(dāng)前計時器retain;
定時器在初始化時候會指定一個target,會導(dǎo)致target無法釋放磁滚。
通過移除定時器可以避免:
viewcontroller中
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self.timer1 invalidate];
[self.timer2 invalidate];
self.timer1 = nil;
self.timer2 = nil;
}
View中
-(void)removeFromSuperview{
[super removeFromSuperview];
[self.timer invalidate];
self.timer = nil;
}
問題二佛吓、Timer不是一種實時機制
在一個循環(huán)中如果RunLoop沒有被識別(這個時間大概在50-100ms)或者當(dāng)前RunLoop在執(zhí)行一個長的call out(例如執(zhí)行某個循環(huán)操作)則NSTimer可能就會存在誤差
如下面一個例子:
@interface TimerRunloopViewController ()
@property (weak, nonatomic) IBOutlet UILabel *labelShow;
@property(nonatomic,strong) NSThread *thread;
@property(nonatomic,strong) NSTimer *timer;
@end
@implementation TimerRunloopViewController
-(NSThread *)thread{
if(_thread == nil){
_thread = [[NSThread alloc]initWithTarget:self selector:@selector(performTask) object:nil];
}
return _thread;
}
-(NSTimer *)timer{
if(_timer == nil){
_timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(timerPerform) userInfo:nil repeats:YES];
}
return _timer;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self TRDataInit];
}
-(void)dealloc{
NSLog(@"timerRunloop dealloc");
}
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
// [self.thread cancel];
[self.timer invalidate];
self.timer = nil;
}
- (void)TRDataInit{
[self.thread start];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
[self.thread cancel];
}
#pragma mark - 線程執(zhí)行任務(wù)
- (void)performTask{
[[NSRunLoop currentRunLoop]addTimer:self.timer forMode:NSDefaultRunLoopMode];
NSLog(@"runloop before performSelector:%@",[NSRunLoop currentRunLoop]);
// 區(qū)分直接調(diào)用和「performSelector:withObject:afterDelay:」區(qū)別,下面的直接調(diào)用無論是否運行RunLoop一樣可以執(zhí)行,但是后者則不行垂攘。
//[self caculate];
[self performSelector:@selector(caculate) withObject:nil afterDelay:2.0];
// 取消當(dāng)前RunLoop中注冊測selector(注意:只是當(dāng)前RunLoop辈毯,所以也只能在當(dāng)前RunLoop中取消)
//[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(caculate) object:nil];
NSLog(@"runloop after performSelector:%@",[NSRunLoop currentRunLoop]);
// 非主線程RunLoop必須手動調(diào)用
[[NSRunLoop currentRunLoop] run];
NSLog(@"注意:如果RunLoop不退出(運行中),這里的代碼并不會執(zhí)行搜贤,RunLoop本身就是一個循環(huán).");
}
#pragma mark - 定時器執(zhí)行任務(wù)
- (void)timerPerform{
static unsigned long count = 0;
if([NSThread currentThread].isCancelled){
[self.timer invalidate];
self.timer = nil;
}
count ++;
NSLog(@"timer count 值 %ld",count);
dispatch_async(dispatch_get_main_queue(), ^{
self.labelShow.text = [NSString stringWithFormat:@"timer count 值 %ld",count];
});
}
- (void)caculate{
for (int i = 0;i < 9999;++i) {
NSLog(@"%i,%@",i,[NSThread currentThread]);
if ([NSThread currentThread].isCancelled) {
return;
}
}
}
如果運行并且不退出上面的程序會發(fā)現(xiàn)谆沃,前兩秒NSTimer可以正常執(zhí)行,但是兩秒后由于同一個RunLoop中循環(huán)操作的執(zhí)行造成定時器跳過了中間執(zhí)行的機會一直到caculator循環(huán)完畢仪芒,這也正說明了NSTimer不是實時系統(tǒng)機制的原因唁影。
但是以上程序還有幾點需要說明一下:
NSTimer會
對Target進行強引用
直到任務(wù)結(jié)束或exit之后才會釋放。如果上面的程序沒有進行線程cancel
而終止任務(wù)則即使關(guān)閉控制器也無法正確釋放掂名。非主線程的RunLoop并不會自動運行(同時注意默認情況下非主線程的RunLoop并不會自動創(chuàng)建据沈,直到第一次使用),RunLoop運行必須要在加入NSTimer或Source0饺蔑、Sourc1锌介、Observer輸入后運行否則會直接退出。例如上面代碼如果
run
放到NSTimer創(chuàng)建之前則既不會執(zhí)行定時任務(wù)也不會執(zhí)行循環(huán)運算。performSelector:withObject:afterDelay:
執(zhí)行的本質(zhì)還是通過創(chuàng)建一個NSTimer然后加入到當(dāng)前線程RunLoop(通過前后兩次打印RunLoop信息可以看到此方法執(zhí)行之后RunLoop的timer會增加1個孔祸。類似的還有performSelector:onThread:withObject:afterDelay:
隆敢,只是它會在另一個線程的RunLoop中創(chuàng)建一個Timer),所以此方法事實上在任務(wù)執(zhí)行完之前會對觸發(fā)對象形成引用崔慧,任務(wù)執(zhí)行完進行釋放(例如上面會對ViewController形成引用,注意:performSelector: withObject:
等方法則等同于直接調(diào)用,原理與此不同)夹界。同時上面的代碼也充分說明了RunLoop是一個循環(huán)事實趾痘,
run
方法之后的代碼不會立即執(zhí)行滥沫,直到RunLoop退出缀辩。上面程序的運行過程中如果突然dismiss健无,則程序的實際執(zhí)行過程要分為兩種情況考慮:如果循環(huán)任務(wù)
caculate
還沒有開始則會在timer中停止timer運行(停止了線程中第一個任務(wù))检疫,然后等待caculate
執(zhí)行并break
(停止線程中第二個任務(wù))后線程任務(wù)執(zhí)行結(jié)束釋放對控制器的引用;如果循環(huán)任務(wù)caculate
執(zhí)行過程中dismiss則caculate
任務(wù)執(zhí)行結(jié)束丹禀,等待timer下個周期運行(因為當(dāng)前線程的RunLoop并沒有退出,timer引用計數(shù)器并不為0)時檢測到線程取消狀態(tài)則執(zhí)行invalidate
方法(第二個任務(wù)也結(jié)束了)鞋怀,此時線程釋放對于控制器的引用双泪。
CADisplayLink
是一個執(zhí)行頻率(fps)和屏幕刷新相同(可以修改preferredFramesPerSecond
改變刷新頻率)的定時器,它也需要加入到RunLoop
才能執(zhí)行密似。
與NSTimer
類似焙矛,CADisplayLink
同樣是基于CFRunloopTimerRef
實現(xiàn),底層使用mk_timer
(可以比較加入到RunLoop前后RunLoop中timer的變化)残腌。和NSTimer相比它精度更高(盡管NSTimer也可以修改精度)村斟,不過和NStimer
類似的是如果遇到大任務(wù)它仍然存在丟幀現(xiàn)象。
通常情況下CADisaplayLink
用于構(gòu)建幀動畫抛猫,看起來相對更加流暢蟆盹,而NSTimer
則有更廣泛的用處。
2闺金、 AutoreleasePool
AutoreleasePool
與RunLoop
并沒有直接的關(guān)系逾滥,之所以將兩個話題放到一起討論最主要的原因是因為在iOS應(yīng)用啟動后會注冊兩個Observer
管理和維護AutoreleasePool
。在應(yīng)用程序剛剛啟動時打印currentRunLoop
可以看到系統(tǒng)默認注冊了很多個Observer
败匹,其中有兩個Observer
的callout
都是_ wrapRunLoopWithAutoreleasePoolHandler
匣距,這兩個是和自動釋放池相關(guān)的兩個監(jiān)聽。
第一個
Observer
會監(jiān)聽RunLoop
的進入哎壳,它會回調(diào)objc_autoreleasePoolPush()
向當(dāng)前的AutoreleasePoolPage
增加一個哨兵對象標(biāo)志創(chuàng)建自動釋放池毅待。這個Observer
的order
是-2147483647
優(yōu)先級最高,確保發(fā)生在所有回調(diào)操作之前归榕。
第二個
Observer
會監(jiān)聽RunLoop
的進入休眠
和即將退出RunLoop
兩種狀態(tài)尸红,在即將進入休眠時會調(diào)用objc_autoreleasePoolPop()
和objc_autoreleasePoolPush()
根據(jù)情況從最新加入的對象一直往前清理直到遇到哨兵對象。而在即將退出RunLoop
時會調(diào)用objc_autoreleasePoolPop()
釋放自動釋放池內(nèi)對象。這個Observer
的order
是2147483647
外里,優(yōu)先級最低怎爵,確保發(fā)生在所有回調(diào)操作之后。
主線程的其他操作通常均在這個AutoreleasePool
之內(nèi)(main函數(shù)中)盅蝗,以盡可能減少內(nèi)存維護操作(當(dāng)然你如果需要顯式釋放【例如循環(huán)】時可以自己創(chuàng)建AutoreleasePool否則一般不需要自己創(chuàng)建)鳖链。
其實在應(yīng)用程序啟動后系統(tǒng)還注冊了其他Observer(例如即將進入休眠時執(zhí)行注冊回調(diào)_UIGestureRecognizerUpdateObserver
用于手勢處理、回調(diào)為_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv
的Observer用于界面實時繪制更新)和多個Source1(例如context為CFMachPort的Source1用于接收硬件事件響應(yīng)進而分發(fā)到應(yīng)用程序一直到UIEvent)墩莫,這里不再一一詳述芙委。
3、UI更新
打印App啟動之后的主線程RunLoop
可以發(fā)現(xiàn)另外一個callout
為_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv
的Observer
狂秦,這個監(jiān)聽專門負責(zé)UI變化后的更新灌侣,比如修改了frame
、調(diào)整了UI層級(UIView/CALayer)
或者手動設(shè)置了setNeedsDisplay/setNeedsLayout
之后就會將這些操作提交到全局容器裂问。而這個Observer
監(jiān)聽了主線程RunLoop
的即將進入休眠
和退出
狀態(tài)侧啼,一旦進入這兩種狀態(tài)則會遍歷所有的UI更新并提交進行實際繪制更新
。
通常情況下這種方式是完美的堪簿,因為除了系統(tǒng)的更新痊乾,還可以利用setNeedsDisplay
等方法手動觸發(fā)下一次RunLoop
運行的更新。但是如果當(dāng)前正在執(zhí)行大量的邏輯運算可能UI的更新就會比較卡椭更,因此facebook推出了AsyncDisplayKit來解決這個問題哪审。AsyncDisplayKit
其實是將UI排版和繪制運算
盡可能放到后臺,將UI的最終更新操作放到主線程(這一步也必須在主線程完成)甜孤,同時提供一套類UIView或CALayer
的相關(guān)屬性,盡可能保證開發(fā)者的開發(fā)習(xí)慣畏腕。這個過程中AsyncDisplayKit
在主線程RunLoop
中增加了一個Observer
監(jiān)聽即將進入休眠
和退出RunLoop
兩種狀態(tài),收到回調(diào)時遍歷隊列中的待處理任務(wù)一一執(zhí)行缴川。
4、 NSURLConnection
NSURLConnection
一旦啟動就會不斷調(diào)用delegate
方法接收數(shù)據(jù)描馅,這樣一個連續(xù)的的動作正是基于RunLoop
來運行把夸。一旦NSURLConnection
設(shè)置了delegate
就會立即創(chuàng)建一個線程com.apple.NSURLConnectionLoader
,同時內(nèi)部啟動RunLoop
并在NSDefaultMode模式
下添加4個Source0
铭污。其中CFHTTPCookieStorage
用于處理cookie
恋日,CFMultiplexerSource
負責(zé)各種delegate回調(diào)
并在回調(diào)中喚醒delegate內(nèi)部的RunLoop
(通常是主線程)來執(zhí)行實際操作。
5嘹狞、AFNetworking
AFURLConnectionOperation
這個類是基于 NSURLConnection
構(gòu)建的岂膳,其希望能在后臺線程接收 Delegate 回調(diào)。為此 AFNetworking 單獨創(chuàng)建了一個線程磅网,并在這個線程中啟動了一個 RunLoop:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
RunLoop 啟動前內(nèi)部必須要有至少一個Timer/Observer/Source
谈截,所以 AFNetworking 在 [runLoop run] 之前先創(chuàng)建了一個新的 NSMachPort
添加進去了。通常情況下,調(diào)用者需要持有這個NSMachPort (mach_port)
并在外部線程通過這個 port 發(fā)送消息到 loop 內(nèi)簸喂;但此處添加 port 只是為了讓 RunLoop 不至于退出毙死,并沒有用于實際的發(fā)送消息。
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
當(dāng)需要這個后臺線程執(zhí)行任務(wù)時喻鳄,AFNetworking 通過調(diào)用 [self performSelector:onThread:]
將這個任務(wù)扔到了后臺線程的 RunLoop 中扼倘。
6、GCD和RunLoop的關(guān)系
RunLoop
本身和GCD
并沒有直接的關(guān)系除呵。當(dāng)調(diào)用了dispatch_async(dispatch_get_main_queue(), ^(void)block)
時libDispatch
會向主線程RunLoop
發(fā)送消息喚醒RunLoop
再菊,RunLoop從消息中獲取block,并且在CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
回調(diào)里執(zhí)行這個block竿奏。不過這個操作僅限于主線程袄简,其他線程dispatch操作是全部由libDispatch驅(qū)動的。
7泛啸、RunLoop的啟動和退出
一绿语、啟動RunLoop的三種方式
通過[NSRunLoop currentRunLoop]或者CFRunLoopGetCurrent()可以獲取當(dāng)前線程的runloop。
- (void)run;
- (void)runUntilDate:(NSDate *)limitDate候址;
- (void)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
這三種方式無論通過哪一種方式啟動runloop吕粹,如果沒有一個輸入源或者timer
附加于runloop
上,runloop
就會立刻退出岗仑。
(1) 第一種方式匹耕,runloop
會一直運行下去(線程常駐
),在此期間會處理來自輸入源的數(shù)據(jù)荠雕,并且會在NSDefaultRunLoopMode模式
下重復(fù)調(diào)用runMode:beforeDate:
方法稳其。
(2) 第二種方式,可以設(shè)置超時時間
炸卑,在超時時間到達之前既鞠,runloop會一直運行,在此期間runloop
會處理來自輸入源的數(shù)據(jù)盖文,并且也會在NSDefaultRunLoopMode
模式下重復(fù)調(diào)用runMode:beforeDate:
方法嘱蛋。
(3) 第三種方式,runloop
會運行一次
五续,超時時間
到達或者第一個input source
被處理洒敏,則runloop
就會退出。
- 前兩種啟動方式會重復(fù)調(diào)用runMode:beforeDate:方法疙驾。
二凶伙、 退出RunLoop的方式
(1) 第一種啟動方式的退出方法
文檔說,如果想退出runloop它碎,不應(yīng)該使用第一種啟動方式來啟動runloop镊靴。
如果runloop沒有input sources或者附加的timer铣卡,runloop就會退出。
雖然這樣可以將runloop退出偏竟,但是蘋果并不建議我們這么做煮落,因為系統(tǒng)內(nèi)部有可能會在當(dāng)前線程的runloop中添加一些輸入源,所以通過手動移除input source或者timer這種方式踊谋,并不能保證runloop一定會退出蝉仇。
(2)第二種啟動方式的退出方法 runUntilDate:
可以通過 設(shè)置超時時間
來退出 runloop
。
(3)第三種啟動方式的退出方法 runMode:beforeDate:
通過這種方式啟動殖蚕,runloop
只會運行一次轿衔,當(dāng)超時時間
到達或者第一個輸入源
被處理,runloop
就會退出睦疫。
- 如果我們想控制
runloop
的退出時機害驹,而不是在處理完一個輸入源事件之后就退出,那么就要重復(fù)調(diào)用runMode:beforeDate:
蛤育,
具體可以參考蘋果文檔給出的方案宛官,如下:
NSRunLoop *myLoop = [NSRunLoop currentRunLoop];
myPort = (NSMachPort *)[NSMachPort port];
[myLoop addPort:_port forMode:NSDefaultRunLoopMode];
BOOL isLoopRunning = YES; // global
while (isLoopRunning && [myLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
//在關(guān)閉runloop的地方
- (void)quitLoop
{
isLoopRunning = NO;
CFRunLoopStop(CFRunLoopGetCurrent());
}
總之
如果不想退出runloop可以使用第一種方式啟動runloop;
使用第二種方式啟動runloop瓦糕,可以通過設(shè)置超時時間來退出底洗;
使用第三種方式啟動runloop,可以通過設(shè)置超時時間或者使用CFRunLoopStop方法來退出咕娄。
8亥揖、更多RunLoop使用
1、使用perfromSelector
在默認模式下設(shè)置圖片圣勒,防止UITableView滾動卡頓
[MyImageView performSelector:@selector(setImage:) withObject:myImage afterDelay:0.0 inModes:@[NSDefaultRunLoopMode]]
2费变、sunnyxx的UITableView+FDTemplateLayoutCell利用Observer在界面空閑狀態(tài)下計算出UITableViewCell的高度并進行緩存
3、老譚的PerformanceMonitor關(guān)于iOS實時卡頓監(jiān)控圣贸,同樣是利用Observer對RunLoop進行監(jiān)視挚歧。
小結(jié):
1、runloop本質(zhì)上是一個do-while循環(huán),一個線程對應(yīng)一個loop旁趟。主線程loop默認開啟昼激,子線程要自己手動開啟庇绽。一個loop鐘有多個模式(mode)锡搜,默認都是default模式。
2瞧掺、子線程备停活(保證線程的長時間存活 ),比如在子線程加一個定時器辟狈,默認執(zhí)行一次就不會再執(zhí)行了
3肠缔、假如在主線程加一個定時器去修改UI,當(dāng)我們滑動界面的時候就會發(fā)現(xiàn)UI不變了夏跷。因為默認是default模式,滑動界面時切換成響應(yīng)模式明未。通過調(diào)整成common模式槽华,就可以解決這個問題了。
4趟妥、重任務(wù)分散猫态,比如批量大圖加載。
參考文獻:
iOS刨根問底-深入理解RunLoop
RunLoop總結(jié):RunLoop的應(yīng)用場景(三)滾動視圖流暢性優(yōu)化
CFRunloop 優(yōu)化TableView加載高清大圖UI卡頓問題披摄。單獨分批加載