RunLoop 案例代碼測(cè)試

RunLoop編程官方文檔翻譯地址:http://m.itdecent.cn/p/4c38d16a29f1。
下面是里面提到的使用RunLoop的案例.官方提到下面的場(chǎng)景應(yīng)該考慮使用RunLoop.(具體見翻譯文檔5.什么時(shí)候會(huì)用一個(gè)run loop)


繼承NSThread創(chuàng)建一個(gè)MyThread類,重寫dealloc方法,打印線程結(jié)束的信息。如果程序存在下面的代碼,startNewThread方法里面的代碼執(zhí)行完后就會(huì)提示線程退出了。沒有辦法控制線程的運(yùn)行時(shí)間。僅僅能開啟輔助線程執(zhí)行代碼,一般情況下這樣也足夠了,所以蘋果官方文檔說明run loop的開啟是運(yùn)用在需要和線程有更多交互的場(chǎng)合上的。

   - (void)viewDidLoad {
      [super viewDidLoad];
      MyThread * thread = [[MyThread alloc]initWithTarget:self       
       selector:@selector(startNewThread) object:nil];
      [thread start];
    }
    -(void)startNewThread{
      NSLog(@"輔助線程執(zhí)行的代碼");
    }

1. 使用端口或者自定義的輸入源和其他線程通信

這種類型的沒有遇到過,文檔后面有一些相應(yīng)地代碼,但是感覺比較復(fù)雜。不知道什么時(shí)候用這種場(chǎng)景,以后用到了補(bǔ)充。

2. 線程上使用定時(shí)器

一般情況下,自己使用定時(shí)器都是在主線程上,主線程的runloop默認(rèn)是開啟的。定時(shí)器提供了一個(gè)可以直接將定時(shí)器添加到當(dāng)前線程的run loop的NSDefaultRunLoopMode構(gòu)造方法,所以很多時(shí)候什么都不需要做,就能正常使用。但是如果在非主線程上添加定時(shí)器就需要手動(dòng)開啟run loop了。如果不開啟的話和上面的代碼沒有什么區(qū)別,線程執(zhí)行完startNewThread的方法后就退出了,會(huì)導(dǎo)致周期性執(zhí)行定時(shí)器任務(wù)根本實(shí)現(xiàn)不了。那如果我在startNewThread方法里面加上一個(gè)do {;} while(1);雖然雖然能防止線程退出,但是線程會(huì)進(jìn)入死循環(huán)同樣無法執(zhí)行定時(shí)器任務(wù)。所以run loop開啟的意義可以使得和線程有更多的交互,讓線程在忙碌的時(shí)候忙碌,不忙碌的時(shí)候休眠。對(duì)比主線程是一樣的,如果主線程有任務(wù)主線程會(huì)執(zhí)行任務(wù),沒有任務(wù)的話就不耗費(fèi)資源。但是并沒有退出(否則app就退出了);

-(void)startNewThread{
    //獲取當(dāng)前線程的runloop對(duì)象
    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
    //結(jié)構(gòu)體
    CFRunLoopObserverContext  context = {0, (__bridge void *)(self), NULL, NULL, NULL};
    //創(chuàng)建runloop觀察者,綁定run loop。
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                    kCFRunLoopAllActivities, YES, 0,myRunLoopObserver, &context);
    if (observer)
    {
        CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];//獲取Core Foundation形式的runloop引用
        //為run loop的默認(rèn)的模式添加觀察者
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }
    // Create and schedule the timer.
    [NSTimer scheduledTimerWithTimeInterval:1 target:self
                                   selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
    
   // NSTimer * timer = [NSTimer timerWithTimeInterval:0.1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        
   //     NSLog(@"純凈的定時(shí)器");
    //}];
    //[myRunLoop addTimer:timer forMode:NSDefaultRunLoopMode];
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
}
?定時(shí)器.png

上面的代碼在輔助線程中,開啟了run loop。并為run loop添加了觀察者和定時(shí)器源,循環(huán)里面讓run loop運(yùn)行3秒鐘。[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];。RunLoop運(yùn)行3秒,然后線程退出,在這3秒期間,正確的在該線程上處理了定時(shí)器的事件,并且觀察者在回調(diào)函數(shù)中觀察到了run loop運(yùn)行過程需要通知給觀察者的狀態(tài)信息。該案例中如果沒有為run loop添加定時(shí)器和觀察者,線程會(huì)如文檔描述的那樣立刻退出。

run loop運(yùn)行過程.png

中文翻譯可以看翻譯文檔4. run loop一些列的事件,fire這個(gè)單詞一直想不出好的中文詞對(duì)應(yīng)。下面的枚舉觀察者可以觀察到的run loop的狀態(tài)。

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),//進(jìn)入runloop,對(duì)應(yīng)步驟1
    kCFRunLoopBeforeTimers = (1UL << 1),//將要處理一個(gè)定時(shí)器,對(duì)應(yīng)步驟2
    kCFRunLoopBeforeSources = (1UL << 2),//將要處理一個(gè)輸入源,對(duì)應(yīng)步驟3
    kCFRunLoopBeforeWaiting = (1UL << 5),//將要進(jìn)入睡眠,對(duì)應(yīng)步驟6
    kCFRunLoopAfterWaiting = (1UL << 6),//已經(jīng)喚醒,但是還沒有處理喚醒run loop的事件,對(duì)應(yīng)步驟8
    kCFRunLoopExit = (1UL << 7),//將要退出,對(duì)應(yīng)步驟10
    kCFRunLoopAllActivities = 0x0FFFFFFFU //表示監(jiān)控所有的活動(dòng)
};

3. 在cocoa應(yīng)用中使用任意一個(gè)performSelector…方法

比較常見的是performSelectorOnMainThread,非主線程中不能更新UI,開啟額外線程進(jìn)行網(wǎng)絡(luò)請(qǐng)求后有時(shí)可以調(diào)用這個(gè)方法在主線程中更新UI。因?yàn)橹骶€程中runloop已經(jīng)開啟了,所以很自然就成功了。但是如果在非主線程執(zhí)行某個(gè)方法。就需要開啟線程的runloop了。系統(tǒng)提供了一系列類似的方法。

performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:

執(zhí)行特定的selector在主線程的下一個(gè)run loop回路。這兩個(gè)方法給你提供了選項(xiàng)來阻斷當(dāng)前線程直到selector被執(zhí)行完畢。

performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:

執(zhí)行特定的selector在任意線程上,這些線程通過NSThread對(duì)象表示。同樣提供了阻斷當(dāng)前線程直到selector被執(zhí)行。

performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:

在當(dāng)前線程上下一個(gè)run loop回路中執(zhí)行selector,并附加了延遲選項(xiàng)。因?yàn)樗却乱粋€(gè)run loop回路到來才執(zhí)行selector,這些方法從當(dāng)前執(zhí)行的代碼中提供了一個(gè)自動(dòng)的微小延遲。多個(gè)排隊(duì)的selector會(huì)按照順序一個(gè)一個(gè)的執(zhí)行。

cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:

讓你取消一個(gè)通過performSelector:withObject:afterDelay: or performSelector:withObject:afterDelay:inModes: method方法發(fā)送到當(dāng)前線程的消息。

*** 這段代碼讓圖片在默認(rèn)的模式下加載,如果用戶在進(jìn)行滑動(dòng)操作,不會(huì)進(jìn)入這個(gè)模式,可以解決部分滑動(dòng)卡頓問題 ***

[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];

4. 使得線程不被殺死去做周期性任務(wù)

可以根據(jù)業(yè)務(wù)需求,設(shè)置runloop運(yùn)行時(shí)間??梢灶愃朴谏厦娴陌咐V芷谛缘脑谛戮€程上執(zhí)行runmethod方法。


下面對(duì)應(yīng)了3和4兩種情況.

- (void)viewDidLoad {
    [super viewDidLoad];
    MyThread * thread = [[MyThread alloc]initWithTarget:self selector:@selector(longrun) object:nil];
    [thread start];
    [NSTimer scheduledTimerWithTimeInterval:0.5 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [self performSelector:@selector(runmethod) onThread:thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];
    }];
}
-(void)longrun{
    NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
   //為了防止runloop退出,添加一個(gè)端口。
    [runLoop addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
    [runLoop runUntilDate:[NSDate distantFuture]];
}
-(void)runmethod
{
    NSLog(@"%@ 輔助線程上執(zhí)行的代碼",[NSThread currentThread]);
}

如果需要某個(gè)線程一直執(zhí)行,等待某個(gè)條件具備時(shí)觸發(fā)可以參考蘋果提供的案例

- (void)skeletonThreadMain
{
    // Set up an autorelease pool here if not using garbage collection.
    BOOL done = NO;
    // Add your sources or timers to the run loop and do any other setup.
    do
    {
        // Start the run loop but return after each source is handled.
        SInt32    result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
        // If a source explicitly stopped the run loop, or if there are no
        // sources or timers, go ahead and exit.
        if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
            done = YES;
        // Check for any other exit conditions here and set the
        // done variable as needed.
    }
    while (!done);

    // Clean up code here. Be sure to release any allocated autorelease pools.
}

CFRunLoopRunInMode在這里表示在kCFRunLoopDefaultMode模式下啟動(dòng),時(shí)間長(zhǎng)度為10秒,如果源的事件被處理runloop應(yīng)該退出,而不是非要等到10秒耗盡。返回結(jié)果如下

  • 時(shí)間耗盡(kCFRunLoopRunTimedOut)
  • CFRunLoopStop函數(shù)調(diào)用導(dǎo)致runloop(kCFRunLoopRunStopped)
  • 源事件被處理了(kCFRunLoopRunHandledSource)
  • 沒有了任何源或定時(shí)器(kCFRunLoopRunFinished)

代碼設(shè)定了一個(gè)標(biāo)志位。并且開啟runloop事件為10秒,當(dāng)然也可以設(shè)定為其他的值。在每個(gè)源得到處理后返回結(jié)果,根據(jù)結(jié)果,狀態(tài)設(shè)定標(biāo)志位狀態(tài)。最后可以進(jìn)行一些線程退出前的操作。在這里可以依然像上面那些方法一樣為runloop添加定時(shí)器和其他輸入源和觀察者。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容