国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

iOS多線程編程Part1/3-NSThread&RunLoop

2019-11-14 19:54:57
字體:
供稿:網(wǎng)友

前言

多線程的價(jià)值無需贅述,對(duì)于App性能和用戶體驗(yàn)都有著至關(guān)重要的意義,在iOS開發(fā)中,Apple提供了不同的技術(shù)支持多線程編程,除了跨平臺(tái)的pthread之外,還提供了NSThread、NSOperationQueue、GCD等多線程技術(shù),從本篇Blog開始介紹這幾種多線程技術(shù)的細(xì)節(jié)。

對(duì)于pthread這種跨平臺(tái)的多線程技術(shù),這本PRogramming with POSIX Threads做了詳細(xì)介紹,不再提及。

NSThread

使用NSThead創(chuàng)建線程有很多方法:

  • +detachNewThreadSelector:toTarget:withObject:類方法直接生成一個(gè)子線程
1
[NSThread detachNewThreadSelector:@selector(threadRoutine:) toTarget:self withObject:nil];
  • 創(chuàng)建一個(gè)NSThread類實(shí)例,然后調(diào)用start方法。
12
NSThread* aThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRoutine:) object:nil];[aThread start];
  • 調(diào)用NSObject的+performSelectorInBackground:withObject:方法生成子線程。
1
[myObj performSelectorInBackground:@selector(threadRoutine:) withObject:nil];
  • 創(chuàng)建一個(gè)NSThread子類,然后調(diào)用子類實(shí)例的start方法,。

創(chuàng)建線程也是有開銷的,iOS下主要成本包括構(gòu)造內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)、棧空間(子線程512KB、主線程1MB,不過可以使用方法-setStackSize:自己設(shè)置,注意必須是4K的倍數(shù),而且最小是16K),創(chuàng)建線程大約需要90毫秒的創(chuàng)建時(shí)間。

第二種和第四種方法創(chuàng)建的線程有個(gè)好處是擁有線程的對(duì)象,因此可以使用performSelector:onThread:withObject:waitUntilDone:在該線程上執(zhí)行方法,這是一種非常方便的線程間通訊的方法(相對(duì)于設(shè)置麻煩的NSPort用于通訊),所要執(zhí)行的方法可以直接添加到目標(biāo)線程的Runloop中執(zhí)行。Apple建議使用這個(gè)接口運(yùn)行的方法不要是耗時(shí)或者頻繁的操作,以免子線程的負(fù)載過重。

第三種方法其實(shí)與第一種方法是一樣的,都會(huì)直接生成一個(gè)子線程。

上面四種方法生成的子線程都是detached狀態(tài),即主線程結(jié)束時(shí)這些線程都會(huì)被直接殺死;如果要生成joinable狀態(tài)的子線程,只能使用pthread接口啦。

如果需要,可以設(shè)置線程的優(yōu)先級(jí)(-setThreadPriority:);如果要在線程中保存一些狀態(tài)信息,還可以使用到-threadDictionary得到一個(gè)NSMutableDictionary,以key-value的方式保存信息用于線程內(nèi)讀寫。

NSThread的入口方法

要寫一個(gè)有效的子線程入口方法需要注意很多問題,示例代碼:

12345678910111213141516171819202122232425
- (void)threadRoutine{    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  BOOL moreWorkToDo = YES;    BOOL exitNow = NO;    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];    NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];    [threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"];  //添加事件源    [self myInstallCustomInputSource];    while (moreWorkToDo && !exitNow)    {        //執(zhí)行線程真正的工作方法,如果完成了可以設(shè)置moreWorkToDo為False        [runLoop runUntilDate:[NSDate date]];        exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];    }    [pool release];}
  • 必須創(chuàng)建一個(gè)NSAutoreleasePool,因?yàn)樽泳€程不會(huì)自動(dòng)創(chuàng)建。同時(shí)要注意這個(gè)pool因?yàn)槭亲钔鈱觩ool,如果線程中要進(jìn)行長時(shí)間的操作生成大量autoreleased的對(duì)象,則只有在該子線程退出時(shí)才會(huì)回收,因此如果線程中會(huì)大量創(chuàng)建autoreleased對(duì)象,那么需要?jiǎng)?chuàng)建額外的NSAutoreleasePool,可以在NSRunloop每次迭代時(shí)創(chuàng)建和銷毀一個(gè)NSAutoreleasePool。
  • 如果你的子線程會(huì)拋出異常,最好在子線程中設(shè)置一個(gè)異常處理函數(shù),因?yàn)槿绻泳€程無法處理拋出的異常,會(huì)導(dǎo)致程序直接Crash關(guān)閉。
  • (可選)設(shè)置Run Loop,如果子線程只是做個(gè)一次性的操作,那么無需設(shè)置Run Loop;如果子線程進(jìn)入一個(gè)循環(huán)需要不斷處理一些事件,那么設(shè)置一個(gè)Run Loop是最好的處理方式,如果需要Timer,那么Run Loop就是必須的。
  • 如果需要在子線程運(yùn)行的時(shí)候讓子線程結(jié)束操作,子線程每次Run Loop迭代中檢查相應(yīng)的標(biāo)志位來判斷是否還需要繼續(xù)執(zhí)行,可以使用threadDictionary以及設(shè)置Input Source的方式來通知這個(gè)子線程。那么什么是Run Loop呢?這是涉及NSThread及線程相關(guān)的編程時(shí)無法回避的一個(gè)問題。

Run Loop

Run Loop本身并不具備并發(fā)執(zhí)行的功能,但是和多線程開發(fā)息息相關(guān),而且概念令人迷惑,相關(guān)的介紹資料也很少,它的主要的特性如下:

  • 每個(gè)線程都有一個(gè)Run Loop,主線程的Run Loop會(huì)在App運(yùn)行時(shí)自動(dòng)運(yùn)行,子線程中需要手動(dòng)運(yùn)行。
  • 每個(gè)Run Loop都會(huì)以一個(gè)模式mode來運(yùn)行,可以使用NSRunLoop的- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate 方法運(yùn)行在某個(gè)特定模式mode。
  • Run Loop的處理兩大類事件源:Timer Source和Input Source(包括performSelector***方法簇、Port或者自定義Input Source),每個(gè)事件源都會(huì)綁定在Run Loop的某個(gè)特定模式mode上,而且只有RunLoop在這個(gè)模式運(yùn)行的時(shí)候才會(huì)觸發(fā)該Timer和Input Source。
  • 如果沒有任何事件源添加到Run Loop上,Run Loop就會(huì)立刻exit。

Run Loop接口

要操作Run Loop,F(xiàn)oundation層和Core Foundation層都有對(duì)應(yīng)的接口可以操作Run Loop。

Foundation層對(duì)應(yīng)的是NSRunLoop:

Core Foundation層對(duì)應(yīng)的是CFRunLoopRef:

兩組接口差不多,不過功能上還是有許多區(qū)別的,例如CF層可以添加自定義Input Source事件源(CFRunLoopSourceRef)和Run Loop觀察者Observer(CFRunLoopObserverRef),很多類似功能的接口特性也是不一樣的。

Run Loop運(yùn)行

Run Loop如何運(yùn)行呢?在上一節(jié)NSThread的入口函數(shù)中使用了一種NSRunLoop的使用場景,再看一例:

12345678910111213141516171819202122232425
- (void)main{    @autoreleasepool {        NSLog(@"starting thread.......");        NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(doTimerTask) userInfo:nil repeats:YES];        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];        [timer release];        while (! self.isCancelled) {            [self doOtherTask];            BOOL ret = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];            NSLog(@"after runloop counting.........: %d", ret);        }        NSLog(@"finishing thread.........");    }}- (void)doTimerTask{    NSLog(@"do timer task");}- (void)doOtherTask{    NSLog(@"do other task");}

我們看到入口方法里創(chuàng)建了一個(gè)NSTimer,并且以NSDefaultRunLoopMode模式加入到當(dāng)前子線程的NSRunLoop中。進(jìn)入循環(huán)后肯定會(huì)執(zhí)行-doOtherTask方式法一次,然后再以NSDefaultRunLoopMode模式運(yùn)行NSRunLoop,如果一次Timer事件觸發(fā)處理后,這個(gè)Run Loop會(huì)返回嗎?答案是不會(huì),Why?

NSRunLoop的底層是由CFRunLoopRef實(shí)現(xiàn)的,你可以想象成一個(gè)循環(huán)或者類似linux下select或者epoll,當(dāng)沒有事件觸發(fā)時(shí),你調(diào)用的Run Loop運(yùn)行方法不會(huì)立刻返回,它會(huì)持續(xù)監(jiān)聽其他事件源,如果需要Run Loop會(huì)讓子線程進(jìn)入sleep等待狀態(tài)而不是空轉(zhuǎn),只有當(dāng)Timer Source或者Input Source事件發(fā)生時(shí),子線程才會(huì)被喚醒,然后處理觸發(fā)的事件,然而由于Timer source比較特殊,Timer Source事件發(fā)生處理后,Run Loop運(yùn)行方法- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;也不會(huì)返回;而其他非Timer事件的觸發(fā)處理會(huì)讓這個(gè)Run Loop退出并返回YES。當(dāng)Run Loop運(yùn)行在一個(gè)特定模式時(shí),如果該模式下沒有事件源,運(yùn)行Run Loop會(huì)立刻返回NO。

NSRunLoop的運(yùn)行接口:

12345678
//運(yùn)行 NSRunLoop,運(yùn)行模式為默認(rèn)的NSDefaultRunLoopMode模式,沒有超時(shí)限制- (void)run;//運(yùn)行 NSRunLoop: 參數(shù)為運(yùn)行模式、時(shí)間期限,返回值為YES表示是處理事件后返回的,NO表示是超時(shí)或者停止運(yùn)行導(dǎo)致返回的- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;//運(yùn)行 NSRunLoop: 參數(shù)為運(yùn)時(shí)間期限,運(yùn)行模式為默認(rèn)的NSDefaultRunLoopMode模式 -(void)runUntilDate:(NSDate *)limitDate;

CFRunLoopRef的運(yùn)行接口:

1234567891011
//運(yùn)行 CFRunLoopRefvoid CFRunLoopRun();//運(yùn)行 CFRunLoopRef: 參數(shù)為運(yùn)行模式、時(shí)間和是否在處理Input Source后退出標(biāo)志,返回值是exit原因SInt32 CFRunLoopRunInMode (mode, seconds, returnAfterSourceHandled);//停止運(yùn)行 CFRunLoopRefvoid CFRunLoopStop( CFRunLoopRef rl );//喚醒 CFRunLoopRefvoid CFRunLoopWakeUp ( CFRunLoopRef rl );

詳細(xì)講解下NSRunLoop的三個(gè)運(yùn)行接口:

  • - (void)run; 無條件運(yùn)行

不建議使用,因?yàn)檫@個(gè)接口會(huì)導(dǎo)致Run Loop永久性的運(yùn)行在NSDefaultRunLoopMode模式,即使使用CFRunLoopStop(runloopRef);也無法停止Run Loop的運(yùn)行,那么這個(gè)子線程就無法停止,只能永久運(yùn)行下去。

  • - (void)runUntilDate:(NSDate *)limitDate; 有一個(gè)超時(shí)時(shí)間限制

比上面的接口好點(diǎn),有個(gè)超時(shí)時(shí)間,可以控制每次Run Loop的運(yùn)行時(shí)間,也是運(yùn)行在NSDefaultRunLoopMode模式。這個(gè)方法運(yùn)行Run Loop一段時(shí)間會(huì)退出給你檢查運(yùn)行條件的機(jī)會(huì),如果需要可以再次運(yùn)行Run Loop。注意CFRunLoopStop(runloopRef);也無法停止Run Loop的運(yùn)行,因此最好自己設(shè)置一個(gè)合理的Run Loop運(yùn)行時(shí)間。示例:

123456
while (!Done){    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate                dateWithTimeIntervalSinceNow:10]];    NSLog(@"exiting runloop.........:");}
  • - (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate; 有一個(gè)超時(shí)時(shí)間限制,而且設(shè)置運(yùn)行模式

這個(gè)接口在非Timer事件觸發(fā)、顯式的用CFRunLoopStop停止Run Loop、到達(dá)limitDate后會(huì)退出返回。如果僅是Timer事件觸發(fā)并不會(huì)讓Run Loop退出返回;如果是PerfromSelector***事件或者其他Input Source事件觸發(fā)處理后,Run Loop會(huì)退出返回YES。示例:

123456
while (!Done){    BOOL ret = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode                                        beforeDate:[NSDate distantFuture]];    NSLog(@"exiting runloop.........: %d", ret);}

那么如何知道一個(gè)Run Loop是因?yàn)槭裁丛騟xit退出的呢?NSRunLoop中沒有接口可以知道,而需要通過Core Foundation的接口來運(yùn)行CFRunLoopRef,NSRunLoop其實(shí)就是CFRunLoopRef的二次封裝。使用CFRunLoop的接口(C的接口)來運(yùn)行Run Loop,有兩個(gè)接口:

  • void CFRunLoopRun(void);

運(yùn)行在默認(rèn)的kCFRunLoopDefaultMode模式下,直到使用CFRunLoopStop接口停止這個(gè)Run Loop,或者Run Loop的所有事件源都被刪除。

  • SInt32 CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);

第一個(gè)參數(shù)是指RunLoop運(yùn)行的模式(例如kCFRunLoopDefaultMode或者kCFRunLoopCommonModes),第二個(gè)參數(shù)是運(yùn)行時(shí)間,第三個(gè)參數(shù)是是否在處理事件后讓Run Loop退出返回。 示例:

1234567891011
while (!self.isCancelled){    [self doOtherTask];    SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 2, YES);    if (result == kCFRunLoopRunStopped)    {        [self cancel];    }    NSLog(@"exit run loop.........: %ld", result);}

如果Run Loop退出返回后,返回值是SInt32類型(signed long),表明Run Loop返回的原因,目前有四種:

123456
enum {    kCFRunLoopRunFinished = 1, //Run Loop結(jié)束,沒有Timer或者其他Input Source    kCFRunLoopRunStopped = 2, //Run Loop被停止,使用CFRunLoopStop停止Run Loop    kCFRunLoopRunTimedOut = 3, //Run Loop超時(shí)    kCFRunLoopRunHandledSource = 4 ////Run Loop處理完事件,注意Timer事件的觸發(fā)是不會(huì)讓Run Loop退出返回的,即使CFRunLoopRunInMode的第三個(gè)參數(shù)是YES也不行};

注意:Run Loop是可以嵌套調(diào)用的(就像NSAutoreleasePool),例如一個(gè)Run Loop運(yùn)行過程中一個(gè)事件觸發(fā)后,那么在觸發(fā)方法里可以再運(yùn)行當(dāng)前子線程的Run Loop,然后由這個(gè)Run Loop等待其他事件觸發(fā)。不過這種嵌套R(shí)un Loop調(diào)用方式我用的比較少。

以上Run Loop運(yùn)行方法參考本文最后的Sample Code自行嘗試。

Run Loop的運(yùn)行模式Mode

iOS下Run Loop的主要運(yùn)行模式mode有:

1) NSDefaultRunLoopMode: 默認(rèn)的運(yùn)行模式,除了NSConnection對(duì)象的事件。

2) NSRunLoopCommonModes: 是一組常用的模式集合,將一個(gè)input source關(guān)聯(lián)到這個(gè)模式集合上,等于將input source關(guān)聯(lián)到這個(gè)模式集合中的所有模式上。在iOS系統(tǒng)中NSRunLoopCommonMode包含NSDefaultRunLoopMode、NSTaskDeathCheckMode、UITrackingRunLoopMode,我有個(gè)timer要關(guān)聯(lián)到這些模式上,一個(gè)個(gè)注冊(cè)很麻煩,我可以用CFRunLoopAddCommonMode([[NSRunLoop currentRunLoop] getCFRunLoop],(__bridge CFStringRef) NSEventTrackingRunLoopMode)將NSEventTrackingRunLoopMode或者其他模式添加到這個(gè)NSRunLoopCommonModes模式中,然后只需要將Timer關(guān)聯(lián)到NSRunLoopCommonModes,即可以實(shí)現(xiàn)Run Loop運(yùn)行在這個(gè)模式集合中任何一個(gè)模式時(shí),這個(gè)Timer都可以被觸發(fā)。默認(rèn)情況下NSRunLoopCommonModes包含了NSDefaultRunLoopMode和UITrackingRunLoopMode。注意:讓Run Loop運(yùn)行在NSRunLoopCommonModes下是沒有意義的,因?yàn)橐粋€(gè)時(shí)刻Run Loop只能運(yùn)行在一個(gè)特定模式下,而不可能是個(gè)模式集合。

3) UITrackingRunLoopMode: 用于跟蹤觸摸事件觸發(fā)的模式(例如UIScrollView上下滾動(dòng)),主線程當(dāng)觸摸事件觸發(fā)時(shí)會(huì)設(shè)置為這個(gè)模式,可以用來在控件事件觸發(fā)過程中設(shè)置Timer。

4) GSEventReceiveRunLoopMode: 用于接受系統(tǒng)事件,屬于內(nèi)部的Run Loop模式。

5) 自定義Mode:可以設(shè)置自定義的運(yùn)行模式Mode,你也可以用CFRunLoopAddCommonMode添加到NSRunLoopCommonModes中。

Run Loop運(yùn)行時(shí)只能以一種固定的模式運(yùn)行,只會(huì)監(jiān)控這個(gè)模式下添加的Timer Source和Input Source,如果這個(gè)模式下沒有相應(yīng)的事件源,Run Loop的運(yùn)行也會(huì)立刻返回的。注意Run Loop不能在運(yùn)行在NSRunLoopCommonModes模式,因?yàn)镹SRunLoopCommonModes其實(shí)是個(gè)模式集合,而不是一個(gè)具體的模式,我可以在添加事件源的時(shí)候使用NSRunLoopCommonModes,只要Run Loop運(yùn)行在NSRunLoopCommonModes中任何一個(gè)模式,這個(gè)事件源都可以被觸發(fā)。

Run Loop的事件源

歸根結(jié)底,Run Loop就是個(gè)處理事件的Loop,可以添加Timer和其他Input Source等各種事件源,如果事件源沒有發(fā)生時(shí),Run Loop就可能讓線程進(jìn)入asleep狀態(tài),而事件源發(fā)生時(shí)就會(huì)喚醒休眠的(asleep)的子線程來處理事件。Run Loop的事件源事件源分兩類:Timer Source和Input Source(包括-performSelector:***API調(diào)用簇,Port Input Source、自定義Input Source)。

從上圖可以看出Run Loop就是處理事件的一個(gè)循環(huán),不同的是Timer Source事件處理后不會(huì)使Run Loop結(jié)束,而Input Source事件處理后會(huì)讓Run Loop退出。因此你需要自己的一個(gè)Loop去不斷運(yùn)行Run Loop來處理事件,就像本文開頭的示例那樣。

細(xì)分下Run Loop的事件源:

1) Timer Souce就是創(chuàng)建Timer添加到Run Loop中,沒啥好說的,Cocoa或者Core Foundation都有相應(yīng)接口實(shí)現(xiàn)。需要注意的是scheduledTimerWith****開頭生成的Timer會(huì)自動(dòng)幫你以默認(rèn)NSDefaultRunLoopMode模式加載到當(dāng)前的Run Loop中,而其他接口生成的Timer則需要你手動(dòng)使用-addTimer:forMode添加到Run Loop中。需要額外注意的是Timer的觸發(fā)不會(huì)讓Run Loop返回。(Timer sources deliver events to their handler routines but do not cause the run loop to exit.) 具體實(shí)驗(yàn)可以看下面的Sample Code。

2) Input Source中的-performSelector:***API調(diào)用簇方法,有以下這些接口:

1234567891011
performSelectorOnMainThread:withObject:waitUntilDone:performSelectorOnMainThread:withObject:waitUntilDone:modes:performSelector:onThread:withObject:waitUntilDone:performSelector:onThread:withObject:waitUntilDone:modes:performSelector:withObject:afterDelay:performSelector:withObject:afterDelay:inModes:cancelPreviousPerformRequestsWithTarget:cancelPreviousPerformRequestsWithTarget:selector:object:

這些API最后兩個(gè)是取消當(dāng)前線程中調(diào)用,其他API是在主線程或者當(dāng)前線程下的Run Loop中執(zhí)行指定的@selector。

3) Port Input Source:概念上也比較簡單,可以用NSMachPort作為線程之間的通訊通道。例如在主線程創(chuàng)建子線程時(shí)傳入一個(gè)NSPort對(duì)象,這樣主線程就可以和這個(gè)子線程通訊啦,如果要實(shí)現(xiàn)雙向通訊,那么子線程也需要回傳給主線程一個(gè)NSPort。

NSPort的子類除了NSMachPort,還可以使用NSMessagePort或者Core Foundation中的CFMessagePortRef。

注意:雖然有這么棒的方式實(shí)現(xiàn)線程間通訊方式,但是估計(jì)是由于危及iOS的Sandbox沙盒環(huán)境,所以這些API都是私有接口,如果你用到NSPortMessage,XCode會(huì)提示'NSPortMessage' for instance message is a forward declaration

4) 自定義Input Source:

向Run Loop添加自定義Input Source只能使用Core Foundation的接口:CFRunLoopSourceCreate創(chuàng)建一個(gè)source,CFRunLoopAddSource向Run Loop中添加source,CFRunLoopRemoveSource從Run Loop中刪除source,CFRunLoopSourceSignal通知source,CFRunLoopWakeUp喚醒Run Loop。

Apple官方文檔提供了一個(gè)自定義Input Source使用模式。

主線程持有包含子線程的Run Loop和Source的context對(duì)象,還有一個(gè)用于保存需要運(yùn)行操作的數(shù)據(jù)buffer。主線程需要子線程干活時(shí),首先將需要的操作數(shù)據(jù)添加到數(shù)據(jù)buffer,然后通知source,喚醒子線程Run Loop(因?yàn)樽泳€程可能正在sleep狀態(tài),CFRunLoopWakeUp喚醒Run Loop可以通知線程醒來干活),由于子線程也持有這個(gè)source和數(shù)據(jù)buffer,因此在觸發(fā)喚醒時(shí)可以使用這個(gè)數(shù)據(jù)buffer的數(shù)據(jù)來執(zhí)行相關(guān)操作(需要注意數(shù)據(jù)buffer訪問時(shí)的同步)。

具體實(shí)現(xiàn)參見本文最后的Sample Code。

Run Loop的Observer

Core Foundation層的接口可以定義一個(gè)Run Loop的觀察者在Run Loop進(jìn)入以下某個(gè)狀態(tài)時(shí)得到通知:

  • Run loop的進(jìn)入
  • Run loop處理一個(gè)Timer的時(shí)刻
  • Run loop處理一個(gè)Input Source的時(shí)刻
  • Run loop進(jìn)入睡眠的時(shí)刻
  • Run loop被喚醒的時(shí)刻,但在喚醒它的事件被處理之前
  • Run loop的終止

Observer的創(chuàng)建以及添加到Run Loop中需要使用Core Foundation的接口:

1234567
CFRunLoopObserverContext  context = {0, (__bridge void *)(self), NULL, NULL, NULL};CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeTimers, YES, 0, &myRunLoopObserver, &context);if (observer){  CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer,                                 kCFRunLoopCommonModes);}

首先創(chuàng)建Observer的context,然后調(diào)用Core Foundation方法CFRunLoopObserverCreate創(chuàng)建Observer,再加入到當(dāng)前線程的Run Loop中,注意CFRunLoopObserverCreate方法的第二個(gè)參數(shù)是Observer觀察類型,有如下幾種:

12345678910
/* Run Loop Observer Activities */typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {    kCFRunLoopEntry = (1UL << 0),    kCFRunLoopBeforeTimers = (1UL << 1),    kCFRunLoopBeforeSources = (1UL << 2),    kCFRunLoopBeforeWaiting = (1UL << 5),    kCFRunLoopAfterWaiting = (1UL << 6),    kCFRunLoopExit = (1UL << 7),    kCFRunLoopAllActivities = 0x0FFFFFFFU};

對(duì)應(yīng)Run Loop的各種事件,kCFRunLoopAllActivities比較特殊,可以觀察所有事件。具體樣例代碼請(qǐng)參考Sample Code。

總結(jié)

Run Loop就是一個(gè)處理事件源的循環(huán),你可以控制這個(gè)Run Loop運(yùn)行多久,如果當(dāng)前沒有事件發(fā)生,Run Loop會(huì)讓這個(gè)線程進(jìn)入睡眠狀態(tài)(避免再浪費(fèi)CPU時(shí)間),如果有事件發(fā)生,Run Loop就處理這個(gè)事件。Run Loop處理事件和發(fā)送給Observer通知的流程如下:

  • 1) 進(jìn)入Run Loop運(yùn)行,此時(shí)會(huì)通知觀察者進(jìn)入Run Loop;
  • 2) 如果有Timer即將觸發(fā)時(shí),通知觀察者;
  • 3) 如果有非Port的Input Sourc即將e觸發(fā)時(shí),通知觀察者;
  • 4)觸發(fā)非Port的Input Source事件源;
  • 5)如果基于Port的Input Source事件源即將觸發(fā)時(shí),立即處理該事件,跳轉(zhuǎn)到步驟9;
  • 6)通知觀察者當(dāng)前線程將進(jìn)入休眠狀態(tài);
  • 7)將線程進(jìn)入休眠狀態(tài)直到有以下事件發(fā)生:基于Port的Input Source被觸發(fā)、Timer被觸發(fā)、Run Loop運(yùn)行時(shí)間到了過期時(shí)間、Run Loop被喚醒。
  • 8) 通知觀察者線程將要被喚醒。
  • 9) 處理被觸發(fā)的事件:
    • 如果是用戶自定義的Timer,處理Timer事件后重新啟動(dòng)Run Loop進(jìn)入步驟2;
    • 如果線程被喚醒又沒有到過期時(shí)間,則進(jìn)入步驟2;
    • 如果是其他Input Source事件源有事件發(fā)生,直接處理這個(gè)事件;
  • 10)到達(dá)此步驟說明Run Loop運(yùn)行時(shí)間到期,或者是非Timer的Input Source事件被處理后,Run Loop將要退出,退出前通知觀察者線程已退出。

什么時(shí)候需要用到Run Loop?官方文檔的建議是:

  • 需要使用Port或者自定義Input Source與其他線程進(jìn)行通訊。
  • 需要在線程中使用Timer。
  • 需要在線程上使用performSelector*****方法。
  • 需要讓線程執(zhí)行周期性的工作。

我個(gè)人在開發(fā)中遇到的需要使用Run Loop的情況有:

  • 使用自定義Input Source和其他線程通信
  • 子線程中使用了定時(shí)器
  • 使用任何performSelector*****到子線程中運(yùn)行方法
  • 使用子線程去執(zhí)行周期性任務(wù)
  • NSURLConnection在子線程中發(fā)起異步請(qǐng)求

Sample Code

RunLoop剛開始用確實(shí)坑很多,理解概念最好的方式還是動(dòng)手寫代碼,寫了個(gè)例子放在GitHub上(工程N(yùn)SThreadExample),歡迎大家討論。

Apple官方也有一個(gè)基于Run Loop的異步網(wǎng)絡(luò)請(qǐng)求示例程序SimpleURLConnections

參考資料

Threading Programming Guide

NSRunLoop Class Reference

CFRunLoop Reference

CFRunLoopObserver Reference


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 新乡市| 石林| 柳河县| 千阳县| 屏东县| 山阳县| 剑河县| 抚松县| 贵港市| 灵石县| 广东省| 嘉峪关市| 赤壁市| 丰城市| 宜宾县| 绥德县| 砀山县| 恩施市| 临安市| 木兰县| 朝阳市| 天镇县| 车险| 镇坪县| 宣汉县| 柳江县| 荣成市| 松滋市| 垣曲县| 南安市| 泰兴市| 若羌县| 崇仁县| 宁夏| 沙河市| 湖州市| 松阳县| 浙江省| 柞水县| 凤冈县| 洛浦县|