作者:Mitchell
Run loop 剖析:
Runloop 接收的輸入事件來自兩種不同的源:輸入源(intput source)和定時(shí)源(timer source)。輸入源傳遞異步事件。通常消息來自于其他線程或程序。定時(shí)源則傳遞同步事件,發(fā)生在特定時(shí)間或者重復(fù)的時(shí)間間隔。兩種源都使用程序的某一特定的處理歷程來處理到達(dá)的時(shí)間。
一、什么是RunLoop
- 基本作用
- 保持程序的持續(xù)運(yùn)行(一個(gè)死循環(huán),使app不斷運(yùn)行)
- 處理App中的各種事件(觸摸、定時(shí)器、Selector)
- 節(jié)省CPU資源、提高程序性能:該做事的時(shí)候做事,該休息的時(shí)候休息。
- 如果沒有RunLoop
int main(int argc,char * argv[]){
NSLog(@"execute main function");---->程序開始
return 0; ------------------------->程序結(jié)束
}
- 有 RunLoop
- 由于 main 函數(shù)里面啟動(dòng)了個(gè) RunLoop,所以程序并不會(huì)馬上退出,保持持續(xù)運(yùn)行狀態(tài)
int main(int argc,char * argv[]){
BOOL running = YES; -------->程序開始
do {------------------------------
// 執(zhí)行各種任務(wù),處理各種事件------持續(xù)運(yùn)行
}while(running);---------------------
return 0;
}
二、main 函數(shù)中的 RunLoop
- UIApplicationMain函數(shù)內(nèi)部就啟動(dòng)了一個(gè)RunLoop
- 所以UIApplicationMain 函數(shù)一直沒有返回,保持了程序的持續(xù)運(yùn)行
- 這個(gè)默認(rèn)啟動(dòng)的 RunLoop 是跟主線程相關(guān)聯(lián)的
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
三、RunLoop 的輸入源
- 輸入源異步的發(fā)送消息給你的線程,時(shí)間的來源取決于輸入源的種類:基于端口的輸入源和自定義輸入源?;诙丝诘妮斎朐幢O(jiān)聽程序相應(yīng)的端口。自定義輸入源則監(jiān)聽自定義的事件源。runloop,不關(guān)心輸入源是基于端口的還是自定義的。系統(tǒng)會(huì)實(shí)現(xiàn)兩種輸入源供你使用。兩類輸入源的區(qū)別在于如何顯示:基于端口的輸入源由內(nèi)核自動(dòng)發(fā)送,而自定義的則需要人工從其他線程發(fā)送。
- 當(dāng)你創(chuàng)建輸入源的時(shí)候,需要將其分配給 runloop 中的一個(gè)或多個(gè)模式。模式只會(huì)在特定事件影響監(jiān)聽的源。大多數(shù)情況下,runloop 運(yùn)行在默認(rèn)模式下,但是你也可以使其運(yùn)行在自定義模式中。若某一源在當(dāng)前模式下不被監(jiān)聽,那么任何生成的消息只在 runloop 運(yùn)行在所關(guān)聯(lián)的模式下才會(huì)被傳遞。
- 基于端口的輸入源
- Cocoa 和 CoreFoundation 內(nèi)置支持使用端口相關(guān)的對(duì)象和函數(shù)來創(chuàng)建基于端口的源。在 Cocoa 里面你從來不需要直接創(chuàng)建輸入源。只要簡(jiǎn)單的創(chuàng)建對(duì)象,并使用 NSPort 的方法將該端口添加到 ruhnloop 中。端口對(duì)象會(huì)自己處理創(chuàng)建和配置的輸入源。
- 配置基于端口的輸入源
配置 NSMachPort 對(duì)象
為了和 NSMachPort 對(duì)象建立穩(wěn)定的本地連接,你需要?jiǎng)?chuàng)建端口對(duì)象并將之加入相應(yīng)的線程的 run loop。當(dāng)運(yùn)行輔助線程的時(shí)候,你傳遞端口對(duì)象到線程的主體入口點(diǎn)。輔助線程可以使用相同的端口對(duì)象將消息返回給原線程。- a) 實(shí)現(xiàn)主線程的代碼
- (void)launchThread
{
NSPort* myPort = [NSMachPort port];
if (myPort)
{
//這個(gè)類持有即將到來的端口消息
[myPort setDelegate:self];
//將端口作為輸入源安裝到當(dāng)前的 runLoop
[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
//當(dāng)前線程去調(diào)起工作線程
[NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:)
toTarget:[MyWorkerClass class] withObject:myPort];
}
為了在線程間建立雙向的通信,你需要讓工作線程在簽到的消息中發(fā)送自己的本地端口到主線程。主線程接收到簽到消息后就可以知道輔助線程運(yùn)行正常,并且??供了發(fā)送消息給輔助線程的方法。
以下代碼顯示了主線程的 handlePortMessage:方法。當(dāng)由數(shù)據(jù)到達(dá)線程的本地端口時(shí),該方法被調(diào)用。當(dāng)簽到消息到達(dá)時(shí),此方法可以直接從輔助線程里面檢索端口并保存下來以備后續(xù)使用。
#define kCheckinMessage 100
//處理從工作線程返回的響應(yīng)
- (void)handlePortMessage:(NSPortMessage *)portMessage
{
//消息的 id
unsigned int message = [portMessage msgid];
//創(chuàng)建遠(yuǎn)的端口
NSPort* distantPort = nil;
if (message == kCheckinMessage)
{
//獲取工作線程關(guān)聯(lián)的端口,并設(shè)置給遠(yuǎn)程端口
distantPort = [portMessage sendPort];
//為了以后的使用保存工作端口
[self storeDistantPort:distantPort];
}
else
{
//處理其他的消息
}
- ***b) 輔助線程的實(shí)現(xiàn)代碼***
對(duì)于輔助工作線程,你必須配置線程使用特定的端口以發(fā)送消息返回給主要線程。
以下顯示了如何設(shè)置工作線程的代碼。創(chuàng)建了線程的自動(dòng)釋放池后,緊接著創(chuàng)建工作對(duì)象驅(qū)動(dòng)線程運(yùn)行。工作對(duì)象的*** sendCheckinMessage: ***方法創(chuàng)建了工作線程的本地端口并發(fā)送簽到消息回主線程。
//根據(jù)端口信息啟動(dòng)線程
+(void)LaunchThreadWithPort:(id)inData
{
//設(shè)置當(dāng)前線程和主線程的通信端口
NSPort* distantPort = (NSPort*)inData;
//獲取當(dāng)前的工作類
MyWorkerClass* workerObj = [[self alloc] init];
//發(fā)送簽到消息
[workerObj sendCheckinMessage:distantPort];
//釋放
[distantPort release];
//讓 runloop 處理事務(wù)
do
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
while (![workerObj shouldExit]);
[workerObj release];
[pool release];
當(dāng)使用 NSMachPort 的時(shí)候,本地和遠(yuǎn)程線程可以使用相同的端口對(duì)象在線程間進(jìn)行單邊通信。換句話說,一個(gè)線程創(chuàng)建的本地端口對(duì)象成為另一個(gè)線程的遠(yuǎn)程端口對(duì)象。
以下代碼輔助線程的簽到例程,該方法為之后的通信設(shè)置自己的本地端口,然后發(fā)送簽到消息給主線程。它使用 LaunchThreadWithPort:方法中收到的端口對(duì)象做為目標(biāo)消息。
//工作線程簽到方法
- (void)sendCheckinMessage:(NSPort*)outPort
{
//保留遠(yuǎn)程端口,以便將來使用
[self setRemotePort:outPort];
//創(chuàng)建并且傳遞工作線程的端口
NSPort* myPort = [NSMachPort port];
[myPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
//創(chuàng)建簽到消息
NSPortMessage* messageObj = [[NSPortMessage alloc] initWithSendPort:outPort
receivePort:myPort components:nil];
if (messageObj)
{
//完成配置消息并立即將其發(fā)送
[messageObj setMsgId:setMsgid:kCheckinMessage];
[messageObj sendBeforeDate:[NSDate date]];
}
- ***配置 NSMessagePort 對(duì)象***
為了和 NSMeaasgePort 的建立穩(wěn)定的本地連接,你不能簡(jiǎn)單的在線程間傳遞端口
對(duì)象。遠(yuǎn)程消息端口必須通過名字來獲得。在 Cocoa 中這需要你給本地端口指定一個(gè)名字,并將名字傳遞到遠(yuǎn)程線程以便遠(yuǎn)程線程可以獲得合適的端口對(duì)象用于通信。以下代碼顯示端口創(chuàng)建,注冊(cè)到你想要使用消息端口的進(jìn)程。
//創(chuàng)建本地消息端口
NSPort* localPort = [[NSMessagePort alloc] init];
//配置對(duì)象并且將其添加到當(dāng)前 runloop 中
[localPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:localPort forMode:NSDefaultRunLoopMode];
//使用一個(gè)特殊的名字注冊(cè)端口,名字必須是唯一的
NSString* localPortName = [NSString stringWithFormat:@"MyPortName"];
[[NSMessagePortNameServer sharedInstance] registerPort:localPort
name:localPortName];
- ***在 Core Foundation 中配置基于端口的源***
這部分介紹了在 Core Foundation 中如何在程序主線程和工作線程間建立雙通道通信。
以下代碼顯示了程序主線程加載工作線程的代碼。第一步是設(shè)置
CFMessagePortRef 不透明類型來監(jiān)聽工作線程的消息。工作線程需要端口的名稱來建立連接,以便使字符串傳遞給工作線程的主入口函數(shù)。在當(dāng)前的用戶上下文中端口名必須是唯一的,否則可能在運(yùn)行時(shí)造成沖突。
#define kThreadStackSize (8 *4096)
OSStatus MySpawnThread()
{
//創(chuàng)建一個(gè)本地的端口來接收響應(yīng)
CFStringRef myPortName;
CFMessagePortRef myPort;
CFRunLoopSourceRef rlSource;
CFMessagePortContext context = {0, NULL, NULL, NULL, NULL};
Boolean shouldFreeInfo;
//創(chuàng)建端口的名稱
myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.myapp.MainThread"));
//創(chuàng)建端口
myPort = CFMessagePortCreateLocal(NULL,
myPortName,
&MainThreadResponseHandler,
&context,
&shouldFreeInfo);
if (myPort != NULL)
{
//端口被成功的創(chuàng)建了,現(xiàn)在為它創(chuàng)建一個(gè) runloop 源
rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
if (rlSource) {
//將源添加到 runloop 中去
CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
//一旦被安裝,這些指針可以被釋放了。
CFRelease(myPort);
CFRelease(rlSource);
}}
//創(chuàng)建線程并且持續(xù)的運(yùn)行
MPTaskID taskID;
return(MPCreateTask(&ServerThreadEntryPoint,
(void*)myPortName,kThreadStackSize,NULL,NULL,
NULL,0,&taskID));
}
端口建立而且線程啟動(dòng)后,主線程在等待線程簽到時(shí)可以繼續(xù)執(zhí)行。當(dāng)簽到消息到達(dá)后,主線程使用 MainThreadResponseHandler 來分發(fā)消息,如下面代碼 所示。這個(gè)函數(shù)??取工作線程的端口名,并創(chuàng)建用于未來通信的管道。
#define kCheckinMessage 100
//獲取主線程端口消息持有者
CFDataRef MainThreadResponseHandler(CFMessagePortRef local,
SInt32 msgid, CFDataRef data, void* info)
{
//如果消息是簽到的消息
if (msgid == kCheckinMessage)
{
//消息端口
CFMessagePortRef messagePort;
//線程名稱
CFStringRef threadPortName;
//數(shù)據(jù)長(zhǎng)度
CFIndex bufferLength = CFDataGetLength(data);
//讀取數(shù)據(jù)流
UInt8* buffer = CFAllocatorAllocate(NULL, bufferLength, 0);
CFDataGetBytes(data, CFRangeMake(0, bufferLength), buffer);
//設(shè)置線程端口名稱
threadPortName = CFStringCreateWithBytes (NULL, buffer,bufferLength,kCFStringEncodingASCII, FALSE);
//你必須獲得一個(gè)遠(yuǎn)程消息端口的名稱
messagePort = CFMessagePortCreateRemote(NULL,(CFStringRef)threadPortName);
//如果有消息端口
if (messagePort) {
//保留線程公共的端口為了將來的引用
AddPortToListOfActiveThreads(messagePort);
//因?yàn)槎丝谝呀?jīng)被之前的方法保留,所以這里將指針釋放
CFRelease(messagePort);
}
//清空,釋放線程端口名稱
CFAllocatorDeallocate(NULL, buffer);
}
else
{
//處理其他的信息
}
return NULL;
}
主線程配置好后,剩下的唯一事情是讓新創(chuàng)建的工作線程創(chuàng)建自己的端口然后簽到。以下代碼 顯示了工作線程的入口函數(shù)。函數(shù)獲取了主線程的端口名并使用它來創(chuàng)建和主線程的遠(yuǎn)程連接。然后這個(gè)函數(shù)創(chuàng)建自己的本地端口號(hào),安裝到線程的 runloop,最后連同本地端口名稱一起發(fā)回主線程簽到。
#工作線程的入口函數(shù)#
OSStatus ServerThreadEntryPoint(void* param)
{
//創(chuàng)建對(duì)主線程的遠(yuǎn)程端口
CFMessagePortRef mainThreadPort;
//獲取主線程的名稱
CFStringRef portName = (CFStringRef)param;
mainThreadPort = CFMessagePortCreateRemote(NULL, portName);
//釋放作為參數(shù)傳遞過去的字符串的指針
CFRelease(portName);
//創(chuàng)建工作線程的端口
CFStringRef myPortName = CFStringCreateWithFormat(NULL, NULL,CFSTR("com.MyApp.Thread-%d"), MPCurrentTaskID());
//在線程的 context 信息中存儲(chǔ)端口的信息以備以后的使用
CFMessagePortContext context = {0, mainThreadPort, NULL, NULL, NULL};
Boolean shouldFreeInfo;
Boolean shouldAbort = TRUE;
CFMessagePortRef myPort = CFMessagePortCreateLocal(NULL, myPortName,
&ProcessClientRequest, &context, &shouldFreeInfo);
if (shouldFreeInfo)
{
//不能創(chuàng)建本地的端口,那么就殺死線程
MPExit(0);
}
CFRunLoopSourceRef rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
if (!rlSource){
//如果沒有創(chuàng)建本地的端口,那么就殺死線程
MPExit(0);
}
//將源添加到當(dāng)前的 runloop 中去
CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
//一旦被安裝完畢,那么就可以被釋放了
CFRelease(myPort);
CFRelease(rlSource);
//將端口名稱和簽到信息打包,并且寫入流
CFDataRef returnData = nil;
CFDataRef outData;
CFIndex stringLength = CFStringGetLength(myPortName);
UInt8* buffer = CFAllocatorAllocate(NULL, stringLength, 0);
CFStringGetBytes(myPortName,
CFRangeMake(0,stringLength),
kCFStringEncodingASCII,
0,
FALSE,
buffer,
stringLength,
NULL);
outData = CFDataCreate(NULL, buffer, stringLength);
CFMessagePortSendRequest(mainThreadPort, kCheckinMessage, outData, 0.1, 0.0, NULL,NULL);
//清空線程數(shù)據(jù)
CFRelease(outData);
CFAllocatorDeallocate(NULL, buffer);
//進(jìn)入 runLoop
CFRunLoopRun();
}
一旦線程進(jìn)入了它的runloop,所有發(fā)送到線程端口的時(shí)間都會(huì)由 ProcessClientRequest 函數(shù)來處理。
- 自定義輸入源
- 為了自定義輸入源,必須使用 Core Foundation里面的 CGRunLoopSourceRef類型相關(guān)的函數(shù)來創(chuàng)建。你可以使用回調(diào)函數(shù)來配置自定義輸入源。Corefondation 會(huì)在配置源的不同地方調(diào)用回調(diào)函數(shù),處理輸入時(shí)間,在源從 runloop 移除的時(shí)候清理它。
- 除了定義在事件到達(dá)時(shí)自定義輸入源的行為,你也必須定義消息傳遞機(jī)制。源的這部分運(yùn)行在單獨(dú)的線程里面,并負(fù)責(zé)在數(shù)據(jù)等待處理的時(shí)候傳遞數(shù)據(jù)給源并源并通知它處理數(shù)據(jù)。消息傳遞機(jī)制的定義取決于你,但是最好不要過于復(fù)雜。
- 創(chuàng)建自定義的輸入源包括定義以下內(nèi)容:
- 輸入源要處理的信息。
- 使感興趣的客戶端知道如何和輸入源交互的調(diào)度例程。
- 處理其他任何客戶端發(fā)送請(qǐng)求的例程。
- 使輸入源失效的取消例程。
- 由于創(chuàng)建輸入源來處理自定義消息,實(shí)際配置選是靈活配置的。調(diào)度例程,處理例程和取消例程都是創(chuàng)建自定義輸入源是最關(guān)鍵的例程。二輸入源其他的大部分行為都發(fā)生在這些例程的外部。比如,由于你決定數(shù)據(jù)傳輸?shù)捷斎朐吹臋C(jī)制,還有輸入源和其他線程的通信機(jī)制也是由你決定。
- 圖 3-2 中,程序的主線程維護(hù)了一個(gè)輸入源的引用,輸入源所需的自定義命令緩沖區(qū)和輸入源所在的 runloop。當(dāng)主線程有任務(wù)需要分發(fā)給工作線程時(shí)候,主線程會(huì)給命令緩沖區(qū)發(fā)送命令和必須的信息來通知工作線程開始執(zhí)行任務(wù)。(因?yàn)橹骶€程和輸入源所在工作線程都可以訪問命令緩沖區(qū),因此這些訪問必須是同步的)一旦命令傳送出去,主線程會(huì)通知輸入源并且喚醒工作線程的 runloop。而一收到喚醒命令,runloop 會(huì)調(diào)用輸入源的處理程序,由它來執(zhí)行命令緩沖區(qū)中響應(yīng)的命令。
3-2.png - 圖3-2 中的輸入源使用了 Objective-C 的對(duì)象輔助 runloop來管理命令緩沖區(qū)。下面代碼給出了改對(duì)象的定義。RunLoopSource 對(duì)象管理著命令緩沖區(qū)并以此來接收其他線程的消息。例子同樣給出了 RunLoopContext 對(duì)象的定義,它是一個(gè)用于傳遞 RunLoopSource 對(duì)象和 runloop 引用給程序主線程的一個(gè)容器。
@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
//這些是 CFRunLoopSourceRef 的回調(diào)函數(shù)
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;
盡管使用 Objective-C 代碼來管理輸入源的自定義數(shù)據(jù),但是將輸入源附加到 runloop 卻需要使用基于 C 的回調(diào)函數(shù)(RunLoopSourceScheduleRoutine)。因?yàn)檫@個(gè)輸入源只有一個(gè)客戶端(主線程),它使用調(diào)度函數(shù)發(fā)送注冊(cè)信息給應(yīng)用程序的委托(delegate)。當(dāng)委托需要和輸入源通信的時(shí)候,它會(huì)使用 RunLoopContext 對(duì)象來完成。
void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
//獲取輸入源
RunLoopSource* obj = (RunLoopSource*)info;
//獲取應(yīng)用程序的委托
AppDelegate* del = [AppDelegate sharedAppDelegate];
//根據(jù) runloop輸入源 和 runloop 獲取 RunLoopContext,并將這個(gè) RunLoopContext 注冊(cè)到主線程,用于委托和輸入源之間的通信。
RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
//在主線程執(zhí)行注冊(cè)源
[del performSelectorOnMainThread:@selector(registerSource:)
withObject:theContext waitUntilDone:NO];
當(dāng)輸入源被告知的時(shí)候回處理自定義數(shù)據(jù)的那個(gè)例程,以下代碼展示了這個(gè)和 RunLoopSource 對(duì)象相關(guān)回調(diào)的例程。這里只是簡(jiǎn)單的讓 RunLoopSource 執(zhí)行 sourceFired 方法,然后繼續(xù)處理在命令緩存區(qū)出現(xiàn)的命令。
void RunLoopSourcePerformRoutine (void *info)
{
RunLoopSource* obj = (RunLoopSource*)info;
[obj sourceFired];
}
使用 CFRunLoopSourceInvalidate 函數(shù)將輸入源從 runloop 中移除,系統(tǒng)會(huì)調(diào)用輸入源的取消例程。可以使用該例程來通知其他客戶端該輸入源已經(jīng)失效,客戶端應(yīng)該釋放輸入源的引用。
void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
//獲取源
RunLoopSource* obj = (RunLoopSource*)info;
//獲取系統(tǒng)代理
AppDelegate* del = [AppDelegate sharedAppDelegate];
//根據(jù) 源和 runloop 獲取 RunLoopContext
RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
//發(fā)送移除源的命令
[del performSelectorOnMainThread:@selector(removeSource:)
withObject:theContext waitUntilDone:YES];
}
- ***安裝輸入源到 RunLoop***
以下代碼顯示了 RunLoopSource 的 init 和 addToCurrentRunloop 的方法。Init 方法創(chuàng)建 CGFunLoopSourceRef 類型,該類型必須被附加到 runloop 里。它將 RunLoopSource 對(duì)象作為上下文引用參數(shù),以便回調(diào)例程持有該對(duì)象的一個(gè)引用指針。輸入源的安裝只在工作線程調(diào)用 addToCurrentRunLoop 方法才發(fā)生,此時(shí) RunLoopSourceScheduledRoutine 被調(diào)用。一旦輸入源被添加到 runloop,線程就運(yùn)行 runloop 并等待事件。
- (id)init
{
CFRunLoopSourceContext context = {0, self, NULL, NULL, NULL, NULL, NULL,
//CFRunLoopSource 輸入源
runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context);
&RunLoopSourceScheduleRoutine,
RunLoopSourceCancelRoutine,
RunLoopSourcePerformRoutine};
//命令數(shù)組
commands = [[NSMutableArray alloc] init];
return self;
}
- (void)addToCurrentRunLoop
{
//獲取當(dāng)前 runloop
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
//將源添加到 runloop
CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
}
- ***協(xié)調(diào)輸入源的客戶端***
為了讓添加的輸入源有用,需要維護(hù)它并從其他線程給它發(fā)送信號(hào)。輸入源的主要工作就是將于輸入源相關(guān)的線程置于休眠狀態(tài)知道有事件發(fā)生。這就意味著程序中的要有其他線程知道該輸入源信息并且有辦法與之通信。
通知客戶端關(guān)于輸入源信息的方法之一就是當(dāng)你的輸入源開始安裝到你的 runloop 上面后阿松注冊(cè)請(qǐng)求。將輸入源注冊(cè)到任意數(shù)量的客戶端,或者通過代理將輸入源注冊(cè)到感興趣的客戶端那。以下代碼顯示了應(yīng)用委托定義的注冊(cè)方法以及它在 RunLoopSource 對(duì)象的調(diào)度函數(shù)被調(diào)用時(shí)如何運(yùn)行。該方法接收 RunloopSource 提供的 RunLoopContext 對(duì)象,然后將其添加到他自己的源列表里面。
//注冊(cè)源
- (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];
}
- ***通知輸入源***
當(dāng)客戶端發(fā)送數(shù)據(jù)到輸入源之后,它必須發(fā)送信號(hào)通知源并且喚醒它的 runloop。發(fā)送信號(hào)給源可以讓 runloop 知道該源已經(jīng)做好處理消息的準(zhǔn)備。而且因?yàn)樾盘?hào)發(fā)送時(shí)線程可能處于休眠,所以必須總是顯示的喚醒 runllop。如果不這樣做的話會(huì)導(dǎo)致延遲處理輸入源。
當(dāng)客戶端準(zhǔn)備好處理加入緩沖區(qū)的命令后會(huì)調(diào)用此方法。
- (void)fireCommandsOnRunLoop:(CFRunLoopRef)runloop
{
//給 runLoop 發(fā)送信號(hào)
CFRunLoopSourceSignal(runLoopSource);
//顯示的啟動(dòng) runLoop
CFRunLoopWakeUp(runloop);
注意:你不應(yīng)該試圖通過自定義輸入源處理一個(gè) SIGHUP 或其他進(jìn)程級(jí)別類型的信號(hào)。CoreFoundation 喚醒 run loop 的函數(shù)不是信號(hào)安全的,不能在你的應(yīng)用信號(hào)處理例程(signalhandler routines)里面使用。關(guān)于更多信號(hào)處理例程,參閱 sigaction 主頁(yè)。
- Cocoa 執(zhí)行 Selector 的源
- 除了基于端口的源,Cocoa 定義了自定義的輸入源,允許你在任何線程中執(zhí)行 seletor。和基于端口的源一樣,執(zhí)行 selector 請(qǐng)求會(huì)在目標(biāo)線程上序列化,減緩許多咋線程上允許多個(gè)方法容易引起的同步問題。不像基于端口的源,一個(gè) selector 執(zhí)行完后會(huì)自動(dòng)從 runloop 里面移除。
- 當(dāng)在其他線程上面執(zhí)行 selector 時(shí)候,目標(biāo)線程需有一個(gè)活動(dòng)的 runloop,對(duì)于你創(chuàng)建的線程,這意味著線程在你顯示的啟動(dòng) runloop 之前處于等待狀態(tài)。由于主線程自己?jiǎn)?dòng)它的 runloop,那么在程序通過委托調(diào)用 applicationDidFinishlaunching:的時(shí)候你會(huì)遇到線程調(diào)用的問題。因?yàn)?RunLoop 通過每次循環(huán)來處理所有隊(duì)列的 selector 的調(diào)用,而不是通過 loop 的迭代來處理 selector。
- 定時(shí)源
- 定時(shí)源在預(yù)設(shè)的時(shí)間點(diǎn)同步方式傳遞消息。定時(shí)器是線程通知自己做某事的一種方法。
- 盡管定時(shí)器可以產(chǎn)生基于時(shí)間的通知,但它并不是實(shí)時(shí)機(jī)制。和輸入源一樣,定時(shí)器也和 runloop 的特定模式相關(guān)。如果定時(shí)器所在的模式當(dāng)前未被 runloop 監(jiān)視,那么定時(shí)器將不會(huì)開始知道 runloop 運(yùn)行在響應(yīng)的模式下。類似的。如果定時(shí)器在 runloop 處理某一事件期間開始,定時(shí)器會(huì)一直等待直到下次 runloop 開始響應(yīng)的處理程序。如果 runloop 不運(yùn)行了,那么定時(shí)器也永遠(yuǎn)不啟動(dòng)。
- 配置定時(shí)源
Cocoa 中可以使用以下 NSTimer 類方法來創(chuàng)建并調(diào)配一個(gè)定時(shí)器:??
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:??
scheduledTimerWithTimeInterval:invocation:repeats:
上述方法創(chuàng)建了定時(shí)器并以默認(rèn)模式把它們添加到當(dāng)前線程的 run loop。
Core Foundation 創(chuàng)建定時(shí)器
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0,
&myCFTimerCallback, &context);
四、RunLoop 對(duì)象
- iOS 中有2套 API 來訪問和使用 RunLoop
- Foundation
- NSRunLoop 的 currentRunLoop 類方法類檢索一個(gè) NSRunLoop 對(duì)象。
- CoreFoundation
- CFRunLoopGetCurrent 函數(shù)
- Foundation
- NSRunLoop 和 CGRunLoopRef 都代表著 RunLoop 對(duì)象
- NSRunLoop 是基于 CFRunLoopRef 的一層 OC 包裝, 所以要了解 RunLoop內(nèi)部結(jié)構(gòu),需要研究 CFRunLoopRef 層面的 API (Core Foundation層面)
//獲取當(dāng)前 runloop(NSRunLoop*)
+ (NSRunLoop *)currentRunLoop;
[NSRunLoop currentRunLoop];
//CFRunLoopRef
- (CFRunLoopRef)getCFRunLoop CF_RETURNS_NOT_RETAINED;
[[NSRunLoop currentRunLoop]getCFRunLoop];
- 在輔助線程運(yùn)行 run loop 之前,必須至少添加已輸入源或定時(shí)器給它。如果 runloop 沒有任何源需要監(jiān)視的話,它會(huì)在你啟動(dòng)的時(shí)候馬上退出。
- (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);
}
五、RunLoop 與線程
- 每條線程都有唯一的一個(gè)與之對(duì)應(yīng)的 RunLoop 對(duì)象
- 主線程的 RunLoop 自動(dòng)創(chuàng)建好了,子線程的 RunLoop 需要主動(dòng)創(chuàng)建
- RunLoop 在第一次獲取時(shí)創(chuàng)建,在線程結(jié)束時(shí)銷毀
六、獲取RunLoop 對(duì)象
- Foundation
//獲得當(dāng)前線程的 RunLoop 對(duì)象
[NSRunLoop currentRunLoop];
//獲得主線程的 RunLoop 對(duì)象
[NSRunLoop mainRunLoop];
- Core Foundation
//當(dāng)前RunLoop
CFRunLoopGetCurrent();
//主線程 RunLoop
CFRunLoopGetMain();
七、NSRunLoop 相關(guān)類
- CoreFoundation 中關(guān)于 RunLoop 的5個(gè)類
- CFRunLoopRef(運(yùn)行循環(huán)對(duì)象)
- CFRunLoopModeRef(1個(gè)runLoop可以有很多個(gè)Mode,1個(gè)Mode可以有很多個(gè)Source Observer Timer,
但是在同一時(shí)刻只能同時(shí)執(zhí)行一種Mode,關(guān)于更多種類的Mode) - CFRunLoopSourceRef(處理事件)
- CFRunLoopTimerRef(處理定時(shí)器相關(guān))
-
CFRunLoopObserverRef(觀察者,觀察是否有事件)
RunLoop.png
- CFRunLoopModeRef 代表 RunLoop 的運(yùn)行模式
- 一個(gè) RunLoop 包含若干個(gè) Mode,每個(gè)Mode 又包含若干個(gè) Source/Timer、Observer
- 每次 RunLoop 啟動(dòng)時(shí),只能制定其中一個(gè) Mode,這個(gè) Mode 被稱作 CurrentMode
- 如果需要切換 Mode,只能退出 Loop,再重新指定一個(gè) Mode 進(jìn)入
- 這樣做主要是為了分割開不同組的 Source/Timer/Observer,讓其互不影響
- 系統(tǒng)默認(rèn)注冊(cè)了 5個(gè)Mode:
-
kCFRunLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個(gè) Mode 下運(yùn)行的 -
UITrackingRunLoopMode:界面跟蹤 Mode,用于ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他Mode 影響 - UIInitializationRunLoopMode:在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè)Mode,啟動(dòng)完成之后就不再使用。
- GSEventReceiveRunLoopMode:接收系統(tǒng)時(shí)間的內(nèi)部 Mode,通常用不到。
-
kCFRunLoopCommonModes(比較特殊):這時(shí)一個(gè)占位用的 Mode,不是一種真正的 Mode。
-
- CFRunLoopSourceRef 是事件源(輸入源)
- Source0:非基于Port的
- Custom Input Sources
- Cocoa Perform Selector Sources
- Source1:基于Port的
- Port- Based Sources
- 舉例:輸出點(diǎn)擊事件的調(diào)用棧,
我們可以清楚的看到runloop中做的是__CFRunLoopDoSource0。
Source0調(diào)用棧.png
- Source0:非基于Port的
- CFRunLoopTimerRef 處理定時(shí)器
-
NSTimer 定時(shí)器調(diào)用棧:__CFRunLoopDoTimer
timer.png - 注意:使用不同種類的 Mode 會(huì)對(duì)定時(shí)器的效果有不同的展現(xiàn)
-
NSDefaultRunLoopMode:將NSTimer添加到主線程N(yùn)SRunLoop的默認(rèn)模式下,只有主線程是默認(rèn)模式下才能執(zhí)行NSTimer(滾動(dòng)scrollView,RunLoop默認(rèn)進(jìn)入Tracking模式,所以NSTimer不會(huì)有效果)。 -
UITrackingRunLoopMode:將NSTimer添加到主線程N(yùn)SRunLoop的追蹤模式下,只有主線程是追蹤模式下才能執(zhí)行NSTimer。(例如滾動(dòng)scrollView的時(shí)候就會(huì)監(jiān)聽到計(jì)時(shí)器) -
NSRunLoopCommonModes:Common是一個(gè)表示,它是將NSDefaultRunLoopMode 和 UITrackingRunLoopMode標(biāo)記為了Common
所以,只要將 timer 添加到 Common 占位模式下,timer就可以在Default和UITrackingRunLoopMode模式下都能運(yùn)行
-
-
- 如果用GCD創(chuàng)建計(jì)時(shí)器:
- GCD 創(chuàng)建的好處,不受 RunLoopMode 的影響。
//1、創(chuàng)建timer
//dispatchQueue:定時(shí)器將來回調(diào)的方法在哪個(gè)線程中執(zhí)行
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
self.timer = timer;
//2.設(shè)置timer
/*
第一個(gè)參數(shù):需要設(shè)置哪個(gè)timer
第二個(gè)參數(shù):指定定時(shí)器開始的時(shí)間
第三個(gè)參數(shù):指定間隔時(shí)間
第四個(gè)參數(shù):定時(shí)器的精準(zhǔn)度,如果傳0代表要求非常精準(zhǔn)(系統(tǒng)會(huì)讓計(jì)時(shí)器執(zhí)行時(shí)間變得更加準(zhǔn)確,性能消耗也會(huì)提高),如果傳入一個(gè)大于0的值,代表我們?cè)试S的誤差
//例如傳入60,就代表允許誤差有60秒
*/
//設(shè)置第一次執(zhí)行的時(shí)間
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
//DISPATCH_TIME_NOW
dispatch_source_set_timer(timer,start , 2 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
//3、設(shè)置timer的回調(diào)
dispatch_source_set_event_handler(timer, ^{
NSLog(@"%@",[NSRunLoop currentRunLoop]);
});
dispatch_resume(timer);
- 在 RunLoop 底層默認(rèn)會(huì)調(diào)用這里
/// 9.1 如果一個(gè) Timer 到時(shí)間了,觸發(fā)這個(gè)Timer的回調(diào)。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 9.2 如果有dispatch到main_queue的block,執(zhí)行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
- CFRunLoopObserverRef觀察者,能夠監(jiān)聽RunLoop狀態(tài)改變
- 監(jiān)聽的時(shí)間點(diǎn):
typedef CF_OPTIONS(CFOptionFlags,CFRunLoopActivity){
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入LOOP
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將進(jìn)入處理Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛才休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出Loop
}
- 監(jiān)聽的代碼:
- (void)viewDidLoad{
[super viewDidLoad];
//1、創(chuàng)建監(jiān)聽對(duì)象
/*
第一個(gè)參數(shù):告訴系統(tǒng)如何給Observer對(duì)象分配存儲(chǔ)空間
第二個(gè)參數(shù):需要監(jiān)聽的類型
第三個(gè)參數(shù):是否需要重復(fù)監(jiān)聽
第四個(gè)參數(shù):優(yōu)先級(jí)
第五個(gè)參數(shù):監(jiān)聽到對(duì)應(yīng)的狀態(tài)之后的回調(diào)
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
};
*/
CFRunLoopObserverRef oberver= CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"進(jìn)入RunLoop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即將處理Timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即將處理source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即將進(jìn)入睡眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"即將醒來");
break;
case kCFRunLoopExit:
NSLog(@"退出");
break;
default:
break;
}
});
//2、給主線程的RunLoop添加監(jiān)聽
/*
第一個(gè)參數(shù):需要監(jiān)聽的 RunLoop 對(duì)象
第二個(gè)參數(shù):給指定的 RunLoop 對(duì)象添加的監(jiān)聽對(duì)象
第三個(gè)參數(shù):在哪種模式下監(jiān)聽
*/
CFRunLoopAddObserver(CFRunLoopGetMain(), oberver, kCFRunLoopCommonModes);
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(demo) userInfo:nil repeats:YES];
}
- (void)demo{
NSLog(@"%s",__func__);
}
我們會(huì)看到這幾行打印會(huì)重復(fù)執(zhí)行
2015-09-06 17:02:04.848 RunLoop觀察者[35817:418636] 即將醒來
2015-09-06 17:02:04.849 RunLoop觀察者[35817:418636] -[ViewController demo]
2015-09-06 17:02:04.849 RunLoop觀察者[35817:418636] 即將處理Timer
2015-09-06 17:02:04.849 RunLoop觀察者[35817:418636] 即將處理source
2015-09-06 17:02:04.849 RunLoop觀察者[35817:418636] 即將進(jìn)入睡眠
2015-09-06 17:02:06.848 RunLoop觀察者[35817:418636] 即將醒來
2015-09-06 17:02:06.849 RunLoop觀察者[35817:418636] -[ViewController demo]
2015-09-06 17:02:06.849 RunLoop觀察者[35817:418636] 即將處理Timer
2015-09-06 17:02:06.849 RunLoop觀察者[35817:418636] 即將處理source
2015-09-06 17:02:06.849 RunLoop觀察者[35817:418636] 即將進(jìn)入睡眠
八、關(guān)于 RunLoop 的理解
- 理解圖(
引用了一張網(wǎng)上很好的圖片):
RunLoop理解圖.png - 一條線程對(duì)應(yīng)一個(gè) RunLoop,主線程的 RunLoop 只要程序已啟動(dòng)就會(huì)默認(rèn)創(chuàng)建并與主線程綁定好,
RunLoop 底層的實(shí)現(xiàn)是通過字典的形式來將 線程 和 RunLoop 來綁定的,RunLoop 可以理解為懶加載,子線程的 RunLoop 可以調(diào)用 currentRunLoop,先從字典里面根據(jù)子線程取,如果沒有就會(huì)去創(chuàng)建并與子線程綁定,保存到字典當(dāng)中。每個(gè) RunLoop 里面有很多的 Mode,每個(gè) Mode 里面又有很多的source、timer、observer。RunLoop 在同一時(shí)刻只能執(zhí)行一種 Mode,當(dāng)執(zhí)行這種 Mode 的時(shí)候,只有這種 Mode 中的source、timer、observer 有效,別的 Mode 無效,這樣做是為了避免邏輯的混亂。 - 執(zhí)行流程:先進(jìn)入 RunLoop,處理系統(tǒng)默認(rèn)事件,觸發(fā)事件的時(shí)候,RunLoop 醒來處理 timer、source0、source1,處理完再睡覺。
- RunLoop 死掉的情況:
- RunLoop 有個(gè)默認(rèn)的超時(shí)時(shí)間.
seconds = 9999999999.0
- 線程掛了。
九、RunLoop 應(yīng)用場(chǎng)景
- NSTimer
- 就是CFRunLoopTimerRef,他們之間是 toll-free bridged 的。一個(gè) NSTimer 注冊(cè)到 RunLoop 后,RunLoop 會(huì)為其重復(fù)的時(shí)間點(diǎn)注冊(cè)好事件。例如 10:00, 10:10, 10:20 這幾個(gè)時(shí)間點(diǎn)。RunLoop為了節(jié)省資源,并不會(huì)在非常準(zhǔn)確的時(shí)間點(diǎn)回調(diào)這個(gè)Timer。Timer 有個(gè)屬性叫做 Tolerance (寬容度),標(biāo)示了當(dāng)時(shí)間點(diǎn)到后,容許有多少最大誤差。
- 如果某個(gè)時(shí)間點(diǎn)被錯(cuò)過了,例如執(zhí)行了一個(gè)很長(zhǎng)的任務(wù),則那個(gè)時(shí)間點(diǎn)的回調(diào)也會(huì)跳過去,不會(huì)延后執(zhí)行。就比如等公交,如果 10:10 時(shí)我忙著玩手機(jī)錯(cuò)過了那個(gè)點(diǎn)的公交,那我只能等 10:20 這一趟了。
- CADisplayLink 是一個(gè)和屏幕刷新率一致的定時(shí)器(但實(shí)際實(shí)現(xiàn)原理更復(fù)雜,和
NSTimer 并不一樣,其內(nèi)部實(shí)際是操作了一個(gè) Source)。如果在兩次屏幕刷新之間執(zhí)行了一個(gè)長(zhǎng)任務(wù),那其中就會(huì)有一幀被跳過去(和 NSTimer 相似),造成界面卡頓的感覺。在快速滑動(dòng)TableView時(shí),即使一幀的卡頓也會(huì)讓用戶有所察覺。
- ImageView顯示
- PerformSelector:
- 當(dāng)調(diào)用 NSObject 的 performSelector:afterDelay:后,世紀(jì)上期內(nèi)部會(huì)創(chuàng)建一個(gè) Timer 并添加到當(dāng)前線程的 RunLoop 中,所以如果當(dāng)前線程沒有 RunLoop,則這個(gè)方法會(huì)失效。
- 當(dāng)調(diào)用 performSelector:onThread: 時(shí),實(shí)際上其會(huì)創(chuàng)建一個(gè) Timer 加到對(duì)應(yīng)的線程去,同樣的,如果對(duì)應(yīng)線程沒有 RunLoop 該方法也會(huì)失效。
-
常駐線程
-
創(chuàng)建一個(gè)線程來處理耗時(shí)且頻繁的操作,例如即時(shí)聊天音頻的壓縮,或者經(jīng)常下載,避免頻繁開啟線程以便提高性能, AFNetWorking就是如此。
-
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
-
自動(dòng)釋放池
- 系統(tǒng)在主線程 RunLoop 里注冊(cè)了
兩個(gè) Observer,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler() -
第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop),其回調(diào)內(nèi)會(huì)調(diào)用_objc_autoreleasePoolPush()創(chuàng)建自動(dòng)釋放池。其 order 是-2147483647,優(yōu)先級(jí)最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。 -
第二個(gè) Observer 監(jiān)視了兩個(gè)事件:BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop()和_objc_autoreleasePoolPush()釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop)時(shí)調(diào)用 _objc_autoreleasePoolPop() 來釋放自動(dòng)釋放池。這個(gè) Observer 的 order 是 2147483647,優(yōu)先級(jí)最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。 - 打印 currentRunLoop 來獲取autoreleasePool 的狀態(tài)
- 系統(tǒng)在主線程 RunLoop 里注冊(cè)了
NSLog(@"%@",[NSRunLoop currentRunLoop]);
- 只有兩種狀態(tài)
_wrapRunLoopWithAutoreleasePoolHandler:activities = 0x1 = 1
_wrapRunLoopWithAutoreleasePoolHandler:activities = 0xa0 = 160
- 對(duì)比 RunLoop 的活動(dòng)狀態(tài):
對(duì)比runLoop
typedef CF_OPTIONS(CFOptionFlags,CFRunLoopActivity){
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入LOOP =1
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer = 2
kCFRunLoopBeforeSources = (1UL << 2), // 即將進(jìn)入處理Source = 4
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠 = 32
kCFRunLoopAfterWaiting = (1UL << 6), // 剛才休眠中喚醒 = 64
kCFRunLoopExit = (1UL << 7), // 即將退出Loop = 128
}
- 得出結(jié)論:
+ _wrapRunLoopWithAutoreleasePoolHandler:activities = 0x1 = 1 = 即將進(jìn)入RunLoop創(chuàng)建一個(gè)自動(dòng)釋放池
+ _wrapRunLoopWithAutoreleasePoolHandler:activities = 0xa0 = 160 = 128+32
+ 32: 即將進(jìn)入休眠 1、銷毀一個(gè)自動(dòng)釋放池 2、創(chuàng)建一個(gè)新的自動(dòng)釋放池
+ 128:即將退出RunLoop 銷毀一個(gè)自動(dòng)釋放池
- 參考文獻(xiàn):這是一篇很好的文章
這是蘋果的各種 RunLoopMode
***多線程編程指南 ***




