NSRunloop簡單細說(九)—— 幾個重要的問題(三)

版本記錄

版本號 時間
V1.0 2017.08.24

前言

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

一敷扫、 Configuring Run Loop Sources - 配置Run Loop的源

以下部分顯示了如何在CococaCore Foundation中設(shè)置不同類型的輸入源。

1. Defining a Custom Input Source - 定義一個自定義輸入源

創(chuàng)建自定義輸入源包括定義以下內(nèi)容:

  • 您希望輸入源處理的信息诚卸。
  • 調(diào)度程序讓感興趣的客戶知道如何聯(lián)系您的輸入源葵第。
  • 執(zhí)行任何客戶端發(fā)送請求的處理程序。
  • 取消例程合溺,使您的輸入源無效卒密。

因為您創(chuàng)建一個自定義輸入源來處理自定義信息,所以實際的配置被設(shè)計為靈活的棠赛。 調(diào)度程序哮奇,處理程序和取消例程是您自定義輸入源幾乎總是需要的關(guān)鍵例程。 然而睛约,大多數(shù)輸入源行為的其余部分發(fā)生在這些處理程序例程之外鼎俘。 例如,您可以定義將數(shù)據(jù)傳遞到輸入源或者將存在的輸入源和其他線程進行通信的機制辩涝。

下圖顯示了自定義輸入源的示例配置贸伐。 在此示例中,應(yīng)用程序的主線程維護對輸入源的引用怔揩、輸入源的自定義命令緩沖區(qū)以及安裝了輸入源的運行循環(huán)捉邢。 當(dāng)主線程有一個任務(wù)需要切換到工作線程時脯丝,它會將命令發(fā)送到命令緩沖區(qū)以及工作線程啟動任務(wù)所需的任何信息。 (因為主線程和工作線程的輸入源都可以訪問命令緩沖區(qū)伏伐,所以該訪問必須被同步)宠进。一旦命令被發(fā)布,主線程就會通知輸入源并喚醒工作線程的運行循環(huán)藐翎。 在接收到喚醒命令后砰苍,運行循環(huán)將調(diào)用輸入源的處理程序,處理命令緩沖區(qū)中發(fā)現(xiàn)的命令阱高。

以下部分說明了上圖中自定義輸入源的實現(xiàn)赚导,并顯示了您需要實現(xiàn)的關(guān)鍵代碼。

Defining the Input Source - 定義輸入源

定義自定義輸入源需要使用Core Foundation例程來配置運行循環(huán)源并將其附加到運行循環(huán)赤惊。 雖然基本的處理程序是基于C的函數(shù)吼旧,但這并不排除您為這些函數(shù)編寫包裝器,并使用Objective-C或C ++來實現(xiàn)代碼的正文未舟。

上圖中引入的輸入源使用Objective-C對象來管理命令緩沖區(qū)并與運行循環(huán)進行協(xié)調(diào)圈暗。 下面代碼顯示了該對象的定義。 RunLoopSource對象管理命令緩沖區(qū)裕膀,并使用該緩沖區(qū)從其他線程接收消息员串。 此列表還顯示了RunLoopContext對象的定義,該對象實際上只是一個用于將RunLoopSource對象和運行循環(huán)引用傳遞給應(yīng)用程序主線程的容器對象昼扛。

//自定義輸入源對象的定義

@interface RunLoopSource : NSObject
{
    CFRunLoopSourceRef runLoopSource;
    NSMutableArray* commands;
}
 
- (id)init;
- (void)addToCurrentRunLoop;
- (void)invalidate;
 
// Handler method
- (void)sourceFired;
 
// Client interface for registering commands to process
- (void)addCommand:(NSInteger)command withData:(id)data;
- (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runloop;
 
@end
 
// These are the CFRunLoopSourceRef callback functions.
void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
void RunLoopSourcePerformRoutine (void *info);
void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
 
// RunLoopContext is a container object used during registration of the input source.
@interface RunLoopContext : NSObject
{
    CFRunLoopRef        runLoop;
    RunLoopSource*        source;
}
@property (readonly) CFRunLoopRef runLoop;
@property (readonly) RunLoopSource* source;
 
- (id)initWithSource:(RunLoopSource*)src andLoop:(CFRunLoopRef)loop;
@end

盡管Objective-C代碼管理輸入源的自定義數(shù)據(jù)寸齐,但是將輸入源附加到運行循環(huán)中則需要基于C的回調(diào)函數(shù)。 當(dāng)您將運行循環(huán)源實際附加到運行循環(huán)時抄谐,將調(diào)用其中的第一個函數(shù)渺鹦,如下面的代碼所示。 因為這個輸入源只有一個客戶端(主線程)蛹含,所以它使用調(diào)度程序函數(shù)來發(fā)送消息毅厚,以便在該線程上向應(yīng)用程序代理注冊自身。 當(dāng)代理想要與輸入源進行通信時浦箱,它會使用RunLoopContext對象中的信息吸耿。

//Scheduling a run loop source

void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
    RunLoopSource* obj = (RunLoopSource*)info;
    AppDelegate*   del = [AppDelegate sharedAppDelegate];
    RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
 
    [del performSelectorOnMainThread:@selector(registerSource:)
                                withObject:theContext waitUntilDone:NO];
}

最重要的回調(diào)例程之一是用于在您的輸入源發(fā)出信號時處理自定義數(shù)據(jù)。 下面代碼顯示了與RunLoopSource對象關(guān)聯(lián)的執(zhí)行回調(diào)例程酷窥。 該函數(shù)簡單地將作業(yè)的請求轉(zhuǎn)發(fā)到sourceFired方法咽安,該方法然后處理命令緩沖區(qū)中存在的任何命令。

void RunLoopSourcePerformRoutine (void *info)
{
    RunLoopSource*  obj = (RunLoopSource*)info;
    [obj sourceFired];
}

如果您使用CFRunLoopSourceInvalidate函數(shù)從運行循環(huán)中刪除輸入源竖幔,系統(tǒng)將調(diào)用輸入源的取消例程板乙。 您可以使用此例程來通知客戶您的輸入源不再有效是偷,并且應(yīng)刪除對其的任何引用拳氢。 列下面代碼顯示了注冊到RunLoopSource對象的取消回調(diào)例程募逞。 此函數(shù)將另一個RunLoopContext對象發(fā)送給應(yīng)用程序委托,但此時請求委托刪除對運行循環(huán)源的引用馋评。

//Invalidating an input source

void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
    RunLoopSource* obj = (RunLoopSource*)info;
    AppDelegate* del = [AppDelegate sharedAppDelegate];
    RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
 
    [del performSelectorOnMainThread:@selector(removeSource:)
                                withObject:theContext waitUntilDone:YES];
}

Installing the Input Source on the Run Loop - 在運行循環(huán)中安裝輸入源

下面代碼顯示了RunLoopSource類的initaddToCurrentRunLoop方法放接。 init方法創(chuàng)建必須實際附加到運行循環(huán)的CFRunLoopSourceRef opaque類型。 它將RunLoopSource對象本身作為上下文信息留特,以便回調(diào)例程具有指向該對象的指針纠脾。 在工作線程調(diào)用addToCurrentRunLoop方法之后,才會安裝輸入源蜕青,此時調(diào)用RunLoopSourceScheduleRoutine回調(diào)函數(shù)苟蹈。 一旦將輸入源添加到運行循環(huán)中,線程就可以運行其運行循環(huán)來等待它右核。

// Installing the run loop source

- (id)init
{
    CFRunLoopSourceContext context = {0, self, NULL, NULL, NULL, NULL, NULL,

    &RunLoopSourceScheduleRoutine,

    RunLoopSourceCancelRoutine,

    RunLoopSourcePerformRoutine};

    runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context);

    commands = [[NSMutableArray alloc] init];

    return self;
}

- (void)addToCurrentRunLoop

{
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();

    CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
}

Coordinating with Clients of the Input Source - 與客戶的輸入源協(xié)調(diào)

為了使您的輸入源變得有用慧脱,您需要操作它并從另一個線程發(fā)出信號。 輸入源的全部要點是將相關(guān)線程置于休眠狀態(tài)贺喝,直到有事情要做菱鸥。 這個事實需要您的應(yīng)用程序中的其他線程知道輸入源,并有一種與之通信的方式躏鱼。

通知客戶您的輸入源的一種方法是在您的輸入源首次安裝在其運行循環(huán)中時發(fā)出注冊請求氮采。 您可以根據(jù)需要向任意多的客戶注冊您的輸入源,也可以將其注冊到一些中央代理機構(gòu)染苛,然后將您的輸入源轉(zhuǎn)交給感興趣的客戶鹊漠。 下面代碼顯示了當(dāng)調(diào)用RunLoopSource對象的調(diào)度程序函數(shù)時由應(yīng)用程序委托定義并調(diào)用的注冊方法。 此方法接收RunLoopSource對象提供的RunLoopContext對象茶行,并將其添加到其源列表中贸呢。 此列表還顯示了從輸入源從其運行循環(huán)中刪除時用于注銷輸入源的例程。

 // Registering and removing an input source with the application delegate

- (void)registerSource:(RunLoopContext*)sourceInfo;
{
    [sourcesToPing addObject:sourceInfo];
}
 
- (void)removeSource:(RunLoopContext*)sourceInfo
{
    id    objToRemove = nil;
 
    for (RunLoopContext* context in sourcesToPing)
    {
        if ([context isEqual:sourceInfo])
        {
            objToRemove = context;
            break;
        }
    }
 
    if (objToRemove)
        [sourcesToPing removeObject:objToRemove];
}

Signaling the Input Source - 給輸入源發(fā)送信號

在將數(shù)據(jù)交給輸入源后拢军,客戶端必須向源發(fā)出信號并喚醒其運行循環(huán)楞陷。 給源發(fā)送信號使運行循環(huán)知道源可以被處理。 并且因為當(dāng)信號發(fā)生時茉唉,線程可能已經(jīng)睡著了固蛾,你應(yīng)該總是明確地喚醒運行循環(huán)。 否則可能導(dǎo)致處理輸入源的延遲度陆。

下面代碼展示的是顯示RunLoopSource對象的fireCommandsOnRunLoop方法艾凯,當(dāng)客戶準(zhǔn)備好來處理他們添加到緩沖區(qū)的命令的源時,客戶端調(diào)用此方法懂傀。

- (void)fireCommandsOnRunLoop:(CFRunLoopRef)runloop
{
    CFRunLoopSourceSignal(runLoopSource);
    CFRunLoopWakeUp(runloop);
}

這里還需要注意:您不應(yīng)該嘗試通過發(fā)送自定義輸入源來處理SIGHUP或其他類型的進程級信號趾诗。 用于喚醒運行循環(huán)的Core Foundation功能不是信號安全的,不應(yīng)該在應(yīng)用程序的信號處理程序中使用。

后記

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末郑兴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贝乎,更是在濱河造成了極大的恐慌情连,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件览效,死亡現(xiàn)場離奇詭異却舀,居然都是意外死亡,警方通過查閱死者的電腦和手機锤灿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門挽拔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人但校,你說我怎么就攤上這事篱昔。” “怎么了始腾?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵州刽,是天一觀的道長。 經(jīng)常有香客問我浪箭,道長穗椅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任奶栖,我火速辦了婚禮匹表,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宣鄙。我一直安慰自己袍镀,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布冻晤。 她就那樣靜靜地躺著苇羡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鼻弧。 梳的紋絲不亂的頭發(fā)上设江,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機與錄音攘轩,去河邊找鬼叉存。 笑死,一個胖子當(dāng)著我的面吹牛度帮,可吹牛的內(nèi)容都是我干的歼捏。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瞳秽!你這毒婦竟也來了瓣履?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤寂诱,失蹤者是張志新(化名)和其女友劉穎拂苹,沒想到半個月后安聘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痰洒,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年浴韭,在試婚紗的時候發(fā)現(xiàn)自己被綠了丘喻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡念颈,死狀恐怖泉粉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情榴芳,我是刑警寧澤嗡靡,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站窟感,受9級特大地震影響讨彼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜柿祈,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一哈误、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧躏嚎,春花似錦蜜自、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至虚茶,卻和暖如春晚缩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背媳危。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工荞彼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人待笑。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓鸣皂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子寞缝,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,514評論 2 348

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