版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2017.08.24 |
前言
NSRunloop
是OC 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的源
以下部分顯示了如何在Cococa
和Core 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
類的init
和addToCurrentRunLoop
方法放接。 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ù)~~~