NSRunloop簡單細(xì)說(八)—— 幾個重要的問題(二)

版本記錄

版本號 時間
V1.0 2017.08.23

前言

NSRunloopOC Foundation框架中非常重要的一個類,很多時候我們會使用它晓淀,但是未必對其有深入的了解,接下來幾篇我就會帶著大家重新學(xué)習(xí)一下NSRunloop這個類,從簡單到復(fù)雜,從基本到深化且警,我會一步步的走完。希望對大家有所幫助礁遣。感興趣的可以看我上一篇振湾。
1. NSRunloop簡單細(xì)說(一)—— 整體了解
2. NSRunloop簡單細(xì)說(二)—— 獲取運行循環(huán)及其模式
3. NSRunloop簡單細(xì)說(三)—— 定時器和端口
4. NSRunloop簡單細(xì)說(四)—— 開啟Runloop
5. NSRunloop簡單細(xì)說(五)—— 調(diào)度和取消消息
6. NSRunloop簡單細(xì)說(六)—— 幾種循環(huán)模式詳細(xì)解析
7. NSRunloop簡單細(xì)說(七)—— 幾個重要的問題(一)

一、Timer Sources - 定時源

定時器來源將在預(yù)設(shè)的時間內(nèi)同步向線程傳送事件亡脸。 定時器是線程通知自己做某事的一種方式押搪。 例如,一旦在來自用戶的連續(xù)關(guān)鍵筆劃之間經(jīng)過一定量的時間浅碾,搜索字段就可以使用定時器來啟動自動搜索大州。 使用這個延遲時間給用戶在開始搜索之前有機會輸入盡可能多的期望的搜索字符串。

雖然它生成基于時間的通知垂谢,但定時器不是實時機制厦画。 與輸入源一樣,定時器與運行循環(huán)的特定模式相關(guān)聯(lián)滥朱。 如果定時器未處于運行循環(huán)當(dāng)前正在監(jiān)視的模式根暑,則在您以其中一個定時器支持的模式運行運行循環(huán)之前,它不會觸發(fā)徙邻。 類似地排嫌,如果在運行循環(huán)處于執(zhí)行處理程序例程的中間時定時器觸發(fā),則定時器等待直到下一次通過運行循環(huán)來調(diào)用其處理程序例程缰犁。 如果運行循環(huán)沒有運行淳地,則定時器不會被觸發(fā)。

您可以配置定時器一次或重復(fù)生成事件帅容。 重復(fù)定時器根據(jù)預(yù)定的開啟時間自動重新調(diào)度颇象,而不是實際的開啟時間。 例如并徘,如果定時器計劃在特定時間和之后的每5秒發(fā)射遣钳,則即使實際的發(fā)射時間延遲,預(yù)定的發(fā)射時間將始終落在原始的5秒時間間隔上麦乞。 如果開啟時間延遲太多蕴茴,以至于它錯過了一個或多個預(yù)定的開啟時間,則定時器在錯過的時間段內(nèi)僅被觸發(fā)一次路幸。 對于錯過的周期荐开,計時器將重新安排下次預(yù)定的開啟時間。


二简肴、Run Loop Observers - 運行循環(huán)觀察

適宜的異步或同步事件發(fā)生后會觸發(fā)事件源的開始晃听,與這些源相反,在運行循環(huán)本身的執(zhí)行期間砰识,運行循環(huán)觀察器在特定的位置觸發(fā)能扒。 您可以使用運行循環(huán)觀察器來準(zhǔn)備您的線程來處理給定的事件,或者在進(jìn)入休眠之前準(zhǔn)備線程辫狼。 您可以在運行循環(huán)中將運行循環(huán)觀察器與以下事件相關(guān)聯(lián):

  • 運行循環(huán)的入口初斑。
  • 當(dāng)運行循環(huán)即將處理定時器時。
  • 當(dāng)運行循環(huán)即將處理輸入源時膨处。
  • 當(dāng)運行循環(huán)即將去休眠時见秤。
  • 當(dāng)運行循環(huán)已經(jīng)被喚醒砂竖,但在它已經(jīng)處理了喚醒它的事件之前。
  • 退出運行循環(huán)鹃答。

您可以使用Core Foundation將運行循環(huán)觀察器添加到應(yīng)用程序乎澄。 要創(chuàng)建一個運行循環(huán)觀察器,您將創(chuàng)建一個CFRunLoopObserverRef不透明類型的新實例测摔。 這種類型跟蹤您的自定義回調(diào)函數(shù)及其感興趣的活動置济。

類似于定時器,可以使用一次或多次運行循環(huán)觀察器锋八。 單次觀察器在觸發(fā)之后將其從運行循環(huán)中刪除浙于,而重復(fù)的觀察器則不會被刪除,您指定觀察者在創(chuàng)建它時是否運行一次或多次挟纱。


三羞酗、The Run Loop Sequence of Events - 事件的運行循環(huán)序列

每次運行它時,線程的運行循環(huán)將處理掛起的事件樊销,并為任何附加的觀察者生成通知整慎。 這樣做的順序是非常明確的,如下所示:

    1. 通知觀察者已輸入運行循環(huán)围苫。
    1. 通知觀察員裤园,任何準(zhǔn)備好的定時器即將開啟。
    1. 通知觀察員剂府,任何不是基于端口的輸入源即將啟動拧揽。
    1. 任何可以啟動的非基于端口的輸入源啟動。
    1. 如果基于端口的輸入源準(zhǔn)備就緒并等待觸發(fā)腺占,請立即處理該事件淤袜。 轉(zhuǎn)到步驟9。
    1. 通知觀察者線程即將睡眠衰伯。
    1. 讓線程睡覺铡羡,直到發(fā)生以下事件之一:
    • 事件到達(dá)基于端口的輸入源。
    • 一個計時器觸發(fā)
    • 為運行循環(huán)設(shè)置的超時值過期意鲸。
    • 運行循環(huán)被明確喚醒烦周。
    1. 通知觀察者線程剛剛醒來。
    1. 處理待處理的事件怎顾。
    • 如果用戶定義的定時器觸發(fā)读慎,則處理定時器事件并重新啟動循環(huán)。 轉(zhuǎn)到步驟2槐雾。
    • 如果輸入源被觸發(fā)夭委,則傳遞該事件。
    • 如果運行循環(huán)被明確喚醒但尚未超時募强,請重新啟動循環(huán)株灸。 轉(zhuǎn)到步驟2崇摄。
    1. 通知觀察者運行循環(huán)已經(jīng)退出。

因為定時器和輸入源的觀察者通知在這些事件實際發(fā)生之前傳遞蚂且,所以通知時間和實際事件的時間之間可能存在差距配猫。 如果這些事件之間的時間是至關(guān)重要的,您可以使用睡眠和睡眠喚醒通知來幫助您將實際事件之間的時間相關(guān)聯(lián)杏死。

因為定時器和其他周期性事件在運行運行循環(huán)時傳遞,規(guī)避該循環(huán)會破壞這些事件的傳遞捆交。 一個典型的例子就是淑翼,當(dāng)您通過輸入循環(huán)并重復(fù)地從應(yīng)用程序請求事件來實現(xiàn)鼠標(biāo)跟蹤時,這種行為就會發(fā)生品追。 因為您的代碼直接抓取事件玄括,而不是讓應(yīng)用程序正常發(fā)送這些事件,所以活動計時器將無法啟動肉瓦,直到您的鼠標(biāo)跟蹤程序退出并返回控制應(yīng)用程序遭京。

運行循環(huán)可以使用運行循環(huán)對象顯式喚醒。 其他事件也可能導(dǎo)致運行循環(huán)被喚醒泞莉。 例如哪雕,添加另一個非基于端口的輸入源喚醒運行循環(huán),以便可以立即處理輸入源鲫趁,而不是等待直到發(fā)生其他一些事件斯嚎。


四、什么時候使用Run Loop?

您唯一需要顯式運行運行循環(huán)就是在為應(yīng)用程序創(chuàng)建輔助線程的時候挨厚,您的應(yīng)用程序主線程的運行循環(huán)是程序的一個關(guān)鍵部分堡僻。 因此,app框架提供了運行主應(yīng)用程序循環(huán)并自動啟動該循環(huán)的代碼疫剃。 iOS中的UIApplication(或OS X中的NSApplication)的運行方法作為正常啟動順序的一部分啟動應(yīng)用程序的主循環(huán)钉疫。 如果您使用Xcode模板項目來創(chuàng)建應(yīng)用程序,則不應(yīng)顯式地調(diào)用這些例程巢价。

對于輔助線程牲阁,你也可以理解為子線程。您需要確定是否需要運行循環(huán)蹄溉,如果是咨油,請自行配置并啟動它。 在所有情況下柒爵,您不需要啟動線程的運行循環(huán)役电。 例如,如果使用線程執(zhí)行一些長時間運行和預(yù)定的任務(wù)棉胀,那么可以避免啟動運行循環(huán)法瑟。 運行循環(huán)旨在用于與線程進(jìn)行更多交互的情況冀膝。 例如,如果您計劃執(zhí)行以下操作霎挟,則需要啟動運行循環(huán):

  • 使用端口或自定義輸入源與其他線程通信窝剖。
  • 在線程上使用計時器。
  • 在Cocoa應(yīng)用程序中使用任何performSelector ...方法酥夭。
  • 保持線程執(zhí)行定期任務(wù)

如果您選擇使用運行循環(huán)赐纱,則配置和設(shè)置很簡單。 與所有線程編程一樣熬北,您應(yīng)該有一個在適當(dāng)情況下退出輔助線程的計劃疙描,讓它明確的退出要比強制它退出要好的多。


五讶隐、使用 Run Loop對象

運行循環(huán)對象提供了將輸入源起胰,計時器和運行循環(huán)觀察器添加到運行循環(huán)然后運行的主接口。 每個線程都有一個與之相關(guān)聯(lián)的運行循環(huán)對象巫延。 在Cocoa中效五,此對象是NSRunLoop類的一個實例。 在低級應(yīng)用程序中炉峰,它是指向CFRunLoopRef不透明類型的指針畏妖。

1. Getting a Run Loop Object - 獲取 Run Loop對象

要獲取當(dāng)前線程的運行循環(huán),請使用以下之一:

  • 在Cocoa應(yīng)用程序中讲冠,使用NSRunLoopcurrentRunLoop類方法來檢索NSRunLoop對象瓜客。
  • 使用CFRunLoopGetCurrent函數(shù)

雖然它們不是自由的橋接類型,但是在需要時可以從NSRunLoop對象獲取CFRunLoopRef不透明類型竿开。 NSRunLoop類定義了一個getCFRunLoop方法谱仪,該方法返回可以傳遞給Core Foundation例程的CFRunLoopRef類型。 因為兩個對象引用相同的運行循環(huán)否彩,所以可以根據(jù)需要將NSRunLoop對象和CFRunLoopRef opaque類型的調(diào)用混合疯攒。

2. Configuring the Run Loop - Run Loop的配置

在子線程上運行運行循環(huán)之前,必須至少添加一個輸入源或計時器列荔。 如果運行循環(huán)沒有任何來源進(jìn)行監(jiān)視敬尺,則當(dāng)您嘗試運行它時立即退出。

除了安裝源之外贴浙,您還可以安裝運行循環(huán)觀察器并使用它們來檢測運行循環(huán)的不同執(zhí)行階段砂吞。 要安裝運行循環(huán)觀察器,請創(chuàng)建CFRunLoopObserverRef opaque類型崎溃,并使用CFRunLoopAddObserver函數(shù)將其添加到運行循環(huán)中蜻直。 必須使用Core Foundation創(chuàng)建運行循環(huán)觀察器,即使對于Cocoa應(yīng)用程序也是如此。

下面代碼顯示了將運行循環(huán)觀察器連接到其運行循環(huán)的線程的主例程概而。 該示例的目的是向您展示如何創(chuàng)建運行循環(huán)觀察器呼巷,因此該代碼只需設(shè)置一個運行循環(huán)觀察器來監(jiān)視所有運行循環(huán)活動。 在處理定時器請求時赎瑰,基本處理程序例程(未顯示)只記錄運行循環(huán)活動王悍。

//創(chuàng)建運行循環(huán)觀察器

- (void)threadMain
{
    // The application uses garbage collection, so no autorelease pool is needed.
    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
    // Create a run loop observer and attach it to the run loop.
    CFRunLoopObserverContext  context = {0, self, NULL, NULL, NULL};
    CFRunLoopObserverRef    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
            kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
 
    if (observer)
    {
        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }
 
    // Create and schedule the timer.
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
                selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
 
    NSInteger    loopCount = 10;
    do
    {
        // Run the run loop 10 times to let the timer fire.
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
    }
    while (loopCount);
}

配置長時間存在的線程的運行循環(huán)時,最好至少添加一個輸入源來接收消息餐曼。 雖然可以只連接一個計時器來進(jìn)入運行循環(huán)压储,但一旦定時器觸發(fā),通常會使其無效源譬,這將導(dǎo)致運行循環(huán)退出渠脉。 安裝重復(fù)的計時器可以使運行循環(huán)在較長的時間內(nèi)運行,但是會涉及到定時喚醒定時器來喚醒你的線程瓶佳,這實際上是另一種形式的輪詢。 相比之下鳞青,輸入源等待事件發(fā)生霸饲,保持線程睡著,直到它發(fā)生臂拓。

3. Starting the Run Loop - 開啟運行循環(huán)

啟動運行循環(huán)僅對應(yīng)用程序中的輔助線程是必需的厚脉。 運行循環(huán)必須至少有一個輸入源或定時器來監(jiān)視。 如果沒有附加胶惰,運行循環(huán)將立即退出傻工。

啟動運行循環(huán)有幾種方法,包括:

  • 無條件
  • 設(shè)定時限
  • 在特定模式下

無條件進(jìn)入你的運行循環(huán)是最簡單的選擇孵滞,但它也是不太可取的中捆。 無條件地運行您的運行循環(huán)將線程放入永久循環(huán),這使您對運行循環(huán)本身的控制能力變的很弱坊饶。 您可以添加和刪除輸入源和計時器泄伪,但停止運行循環(huán)的唯一方法是將其刪除。 也沒有辦法在自定義模式下運行運行循環(huán)匿级。

相對無條件地運行運行循環(huán)蟋滴,最好用超時值運行運行循環(huán)。 當(dāng)您使用超時值時痘绎,運行循環(huán)將運行津函,直到事件到達(dá)或分配的時間到期。 如果一個事件到達(dá)孤页,則將該事件分派到處理程序進(jìn)行處理尔苦,然后運行循環(huán)退出。 您的代碼可以重新啟動運行循環(huán)來處理下一個事件。 如果分配的時間到期蕉堰,您可以簡單地重新啟動運行循環(huán)凌净,或者使用時間來進(jìn)行所需的內(nèi)務(wù)管理。

除了超時值之外屋讶,您還可以使用特定模式運行運行循環(huán)冰寻。 模式和超時值不是互斥的,并且可以在啟動運行循環(huán)時使用皿渗。 模式限制將事件傳遞到運行循環(huán)的源的類型斩芭。

下面代碼顯示了線程主入口例程。 該示例的關(guān)鍵部分顯示了運行循環(huán)的基本結(jié)構(gòu)乐疆。 實質(zhì)上划乖,您將輸入源和計時器添加到運行循環(huán)中,然后重復(fù)調(diào)用其中一個例程來啟動運行循環(huán)挤土。 每次運行循環(huán)例程返回時琴庵,您都會檢查是否出現(xiàn)任何可能需要退出線程的條件。 該示例使用Core Foundation運行循環(huán)例程仰美,以便它可以檢查返回結(jié)果并確定運行循環(huán)退出的原因迷殿。 您也可以使用NSRunLoop類的方法以類似方式運行運行循環(huán),如果您使用Cocoa并且不需要檢查返回值咖杂。

//開啟運行循環(huán)
- (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.
}

可以遞歸運行一個運行循環(huán)庆寺。 換句話說,您可以調(diào)用CFRunLoopRun诉字,CFRunLoopRunInMode或任何NSRunLoop方法從輸入源或計時器的處理程序中啟動運行循環(huán)懦尝。 當(dāng)這樣做時,您可以使用任何要運行嵌套運行循環(huán)的模式壤圃,包括外部運行循環(huán)使用的模式陵霉。

4. Exiting the Run Loop - 退出運行循環(huán)

在處理事件之前,有兩種方法使運行循環(huán)退出:

  • 配置運行循環(huán)以超時值運行埃唯。
  • 告訴運行循環(huán)停止撩匕。

使用超時值當(dāng)然是首選,如果您可以管理它墨叛。 指定超時值可讓運行循環(huán)在退出之前完成其所有正常處理止毕,包括傳遞運行循環(huán)觀察器的通知。

使用CFRunLoopStop函數(shù)顯式停止運行循環(huán)會產(chǎn)生類似于超時的結(jié)果漠趁。 運行循環(huán)發(fā)出任何剩余的運行循環(huán)通知扁凛,然后退出。 不同之處在于闯传,您可以在無條件啟動的運行循環(huán)中使用此技術(shù)谨朝。

雖然刪除運行循環(huán)的輸入源和定時器也可能導(dǎo)致運行循環(huán)退出,但這不是停止運行循環(huán)的可靠方法。 一些系統(tǒng)例程將輸入源添加到運行循環(huán)以處理所需的事件字币。 因為你的代碼可能不知道這些輸入源则披,它將無法刪除它們,這將阻止運行循環(huán)退出洗出。

5. Thread Safety and Run Loop Objects - 線程安全和 Run Loop對象

線程安全性取決于您用來操作運行循環(huán)的API士复。 Core Foundation中的功能通常是線程安全的,可以從任何線程調(diào)用翩活。 但是阱洪,如果您正在執(zhí)行更改運行循環(huán)的配置的操作,那么盡可能從擁有運行循環(huán)的線程執(zhí)行此操作仍然是最佳做法菠镇。

Cocoa NSRunLoop類并不像Core Foundation對象那樣固有線程安全冗荸。 如果您正在使用NSRunLoop類來修改運行循環(huán),那么只能從擁有該運行循環(huán)的同一線程執(zhí)行此操作利耍。 將輸入源或計時器添加到屬于不同線程的運行循環(huán)可能會導(dǎo)致您的代碼崩潰或出現(xiàn)意想不到的方式行為蚌本。

后記

未完,待續(xù)~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末隘梨,一起剝皮案震驚了整個濱河市魂毁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌出嘹,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咬崔,死亡現(xiàn)場離奇詭異税稼,居然都是意外死亡,警方通過查閱死者的電腦和手機垮斯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門郎仆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人兜蠕,你說我怎么就攤上這事扰肌。” “怎么了熊杨?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵曙旭,是天一觀的道長。 經(jīng)常有香客問我晶府,道長桂躏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任川陆,我火速辦了婚禮剂习,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己鳞绕,他們只是感情好失仁,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著们何,像睡著了一般萄焦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上垂蜗,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天楷扬,我揣著相機與錄音,去河邊找鬼贴见。 笑死烘苹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的片部。 我是一名探鬼主播镣衡,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼档悠!你這毒婦竟也來了廊鸥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤辖所,失蹤者是張志新(化名)和其女友劉穎惰说,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缘回,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡吆视,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了酥宴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啦吧。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拙寡,靈堂內(nèi)的尸體忽然破棺而出授滓,到底是詐尸還是另有隱情,我是刑警寧澤肆糕,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布般堆,位于F島的核電站,受9級特大地震影響诚啃,放射性物質(zhì)發(fā)生泄漏郁妈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一绍申、第九天 我趴在偏房一處隱蔽的房頂上張望噩咪。 院中可真熱鬧顾彰,春花似錦、人聲如沸胃碾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仆百。三九已至厕隧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間俄周,已是汗流浹背吁讨。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留峦朗,地道東北人建丧。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像波势,于是被迫代替她去往敵國和親翎朱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359

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