RunLoop 知識詳解

一讹剔、簡介

RunLoop是一個對象,這個對象在循環(huán)中用來處理程序運行過程中出現(xiàn)的各種事件(比如說觸摸事件详民、UI刷新事件延欠、定時器事件、Selector事件)沈跨,從而保持程序的持續(xù)運行由捎;而且在沒有事件處理的時候,會進入睡眠模式饿凛,從而節(jié)省CPU資源狞玛,提高程序性能。

OSX/iOS 系統(tǒng)中笤喳,提供了兩個這樣的對象:NSRunLoopCFRunLoopRef为居。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)部的邏輯

image

實際上 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)機制的原因唁影。

image
image
但是以上程序還有幾點需要說明一下:
  1. NSTimer會對Target進行強引用直到任務(wù)結(jié)束或exit之后才會釋放。如果上面的程序沒有進行線程cancel而終止任務(wù)則即使關(guān)閉控制器也無法正確釋放掂名。

  2. 非主線程的RunLoop并不會自動運行(同時注意默認情況下非主線程的RunLoop并不會自動創(chuàng)建据沈,直到第一次使用),RunLoop運行必須要在加入NSTimer或Source0饺蔑、Sourc1锌介、Observer輸入后運行否則會直接退出。例如上面代碼如果run放到NSTimer創(chuàng)建之前則既不會執(zhí)行定時任務(wù)也不會執(zhí)行循環(huán)運算。

  3. 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)用,原理與此不同)夹界。

  4. 同時上面的代碼也充分說明了RunLoop是一個循環(huán)事實趾痘,run方法之后的代碼不會立即執(zhí)行滥沫,直到RunLoop退出缀辩。

  5. 上面程序的運行過程中如果突然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

AutoreleasePoolRunLoop 并沒有直接的關(guān)系逾滥,之所以將兩個話題放到一起討論最主要的原因是因為在iOS應(yīng)用啟動后會注冊兩個Observer管理和維護AutoreleasePool。在應(yīng)用程序剛剛啟動時打印currentRunLoop可以看到系統(tǒng)默認注冊了很多個Observer败匹,其中有兩個Observercallout都是_ wrapRunLoopWithAutoreleasePoolHandler匣距,這兩個是和自動釋放池相關(guān)的兩個監(jiān)聽。

image
image

第一個Observer會監(jiān)聽RunLoop的進入哎壳,它會回調(diào)objc_autoreleasePoolPush()向當(dāng)前的AutoreleasePoolPage增加一個哨兵對象標(biāo)志創(chuàng)建自動釋放池毅待。這個Observerorder-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)對象。這個Observerorder2147483647外里,優(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__CFRunLoopObservermPvObserver狂秦,這個監(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卡頓問題披摄。單獨分批加載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亲雪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子疚膊,更是在濱河造成了極大的恐慌义辕,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寓盗,死亡現(xiàn)場離奇詭異灌砖,居然都是意外死亡,警方通過查閱死者的電腦和手機贞让,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門周崭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人喳张,你說我怎么就攤上這事续镇。” “怎么了销部?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵摸航,是天一觀的道長。 經(jīng)常有香客問我舅桩,道長酱虎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任擂涛,我火速辦了婚禮读串,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘撒妈。我一直安慰自己恢暖,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布狰右。 她就那樣靜靜地躺著杰捂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪棋蚌。 梳的紋絲不亂的頭發(fā)上嫁佳,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天挨队,我揣著相機與錄音,去河邊找鬼蒿往。 笑死盛垦,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瓤漏。 我是一名探鬼主播情臭,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼赌蔑!你這毒婦竟也來了俯在?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤娃惯,失蹤者是張志新(化名)和其女友劉穎跷乐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趾浅,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡愕提,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了皿哨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浓镜。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡拣技,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沪么,我是刑警寧澤键耕,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布咧叭,位于F島的核電站葱跋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏崔步。R本人自食惡果不足惜稳吮,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望井濒。 院中可真熱鬧灶似,春花似錦、人聲如沸瑞你。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捏悬。三九已至撞蚕,卻和暖如春润梯,著一層夾襖步出監(jiān)牢的瞬間过牙,已是汗流浹背甥厦。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留寇钉,地道東北人刀疙。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像扫倡,于是被迫代替她去往敵國和親谦秧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • iOS刨根問底-深入理解RunLoop 2017-05-08 10:35 by KenshinCui 概述 Run...
    mengjz閱讀 1,554評論 1 10
  • iOS刨根問底-深入理解RunLoop 概述 RunLoop作為iOS中一個基礎(chǔ)組件和線程有著千絲萬縷的關(guān)系撵溃,同時...
    reallychao閱讀 820評論 0 6
  • 概述 RunLoop作為iOS中一個基礎(chǔ)組件和線程有著千絲萬縷的關(guān)系疚鲤,同時也是很多常見技術(shù)的幕后功臣。盡管在平時多...
    sumrain_cloud閱讀 943評論 0 5
  • 注:本篇博客只在 ibireme 的 深入理解RunLoop 基礎(chǔ)上做了點方便自己復(fù)習(xí)該知識點的修改缘挑,能力有限集歇,如...
    AidenRao閱讀 2,825評論 6 26
  • 經(jīng)過兩個禮拜加班,年會的籌備終于接近尾聲语淘。 這周的例會內(nèi)容诲宇,多半與年會相關(guān)工作有關(guān)。因為項目現(xiàn)場辦公室新成立惶翻,要安...
    梁京梁京閱讀 331評論 0 1