版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2017.08.24 |
前言
NSRunloop
是OC Foundation
框架中非常重要的一個類霹陡,很多時候我們會使用它,但是未必對其有深入的了解潜慎,接下來幾篇我就會帶著大家重新學(xué)習(xí)一下NSRunloop
這個類虱朵,從簡單到復(fù)雜余境,從基本到深化,我會一步步的走完毁渗。希望對大家有所幫助践磅。感興趣的可以看我上一篇。
1. NSRunloop簡單細(xì)說(一)—— 整體了解
2. NSRunloop簡單細(xì)說(二)—— 獲取運(yùn)行循環(huán)及其模式
3. NSRunloop簡單細(xì)說(三)—— 定時器和端口
4. NSRunloop簡單細(xì)說(四)—— 開啟Runloop
5. NSRunloop簡單細(xì)說(五)—— 調(diào)度和取消消息
6. NSRunloop簡單細(xì)說(六)—— 幾種循環(huán)模式詳細(xì)解析
7. NSRunloop簡單細(xì)說(七)—— 幾個重要的問題(一)
8. NSRunloop簡單細(xì)說(八)—— 幾個重要的問題(二)
9. NSRunloop簡單細(xì)說(九)—— 幾個重要的問題(三)
一灸异、Configuring Timer Sources - 配置定時源
要創(chuàng)建計(jì)時器源府适,您只需創(chuàng)建一個計(jì)時器對象并在運(yùn)行循環(huán)中調(diào)度它。 在Cocoa中肺樟,您可以使用NSTimer類來創(chuàng)建新的定時器對象檐春,而在Core Foundation
中,您可以使用CFRunLoopTimerRef opaque
類型么伯。 在內(nèi)部疟暖,NSTimer類只是Core Foundation的擴(kuò)展,它提供了一些方便的功能,如使用相同方法創(chuàng)建和計(jì)劃定時器的能力俐巴。
在Cocoa中骨望,您可以使用以下任一類方法一次創(chuàng)建和調(diào)度計(jì)時器:
- scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
- scheduledTimerWithTimeInterval:invocation:repeats:
這些方法創(chuàng)建定時器,并以默認(rèn)模式(NSDefaultRunLoopMode
)將其添加到當(dāng)前線程的運(yùn)行循環(huán)中欣舵。 你也可以手動調(diào)度計(jì)時器擎鸠,如果您想通過創(chuàng)建NSTimer對象然后使用NSRunLoop
的addTimer:forMode:
方法將其添加到運(yùn)行循環(huán)中。 這兩種技術(shù)基本上都是一樣的邻遏,但是給你不同程度的控制定時器的配置糠亩。 例如,如果創(chuàng)建定時器并手動將其添加到運(yùn)行循環(huán)中准验,則可以使用除默認(rèn)模式之外的模式來執(zhí)行此操作。 下面代碼顯示了如何使用這兩種技術(shù)創(chuàng)建定時器廷没。 第一個定時器的初始延遲為1秒糊饱,但隨后每0.1秒鐘定時觸發(fā)。 第二個定時器在初始0.2秒延遲之后開始觸發(fā)颠黎,然后每0.2秒觸發(fā)一次另锋。
//Creating and scheduling timers using NSTimer
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create and schedule the first timer.
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
interval:0.1
target:self
selector:@selector(myDoFireTimer1:)
userInfo:nil
repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
// Create and schedule the second timer.
[NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:@selector(myDoFireTimer2:)
userInfo:nil
repeats:YES];
下面代碼展示了使用Core Foundation
功能配置定時器所需的代碼。 雖然此示例不會在上下文結(jié)構(gòu)中傳遞任何用戶定義的信息狭归,但您可以使用此結(jié)構(gòu)傳遞定時器所需的任何自定義數(shù)據(jù)夭坪。
//Creating and scheduling a timer using Core Foundation
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0, &myCFTimerCallback, &context);
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);
二、Configuring a Port-Based Input Source - 配置基于Port的輸入源
Cocoa
和Core Foundation
都提供基于端口的對象过椎,用于線程之間或進(jìn)程之間的通信室梅。 以下部分將介紹如何使用幾種不同類型的端口設(shè)置端口通信。
1. Configuring an NSMachPort Object - NSMachPort對象的配置
要建立與NSMachPort
對象的本地連接疚宇,您將創(chuàng)建端口對象并將其添加到主線程的運(yùn)行循環(huán)中亡鼠。 啟動次要線程時,將相同的對象傳遞給線程的入口點(diǎn)函數(shù)敷待。 輔助線程可以使用相同的對象將消息發(fā)送回主線程间涵。
Implementing the Main Thread Code
下面代碼顯示了啟動輔助工作線程的主線程代碼。 因?yàn)镃ocoa框架執(zhí)行了許多用于配置端口和運(yùn)行循環(huán)的介入步驟榜揖,所以launchThread
方法明顯短于其Core Foundation
中相應(yīng)的方法勾哩;然而,兩者的行為幾乎相同举哟。 一個區(qū)別是思劳,該方法不是將本地端口的名稱發(fā)送給工作線程,而是直接發(fā)送NSPort
對象炎滞。
//Main thread launch method
- (void)launchThread
{
NSPort* myPort = [NSMachPort port];
if (myPort)
{
// This class handles incoming port messages.
[myPort setDelegate:self];
// Install the port as an input source on the current run loop.
[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
// Detach the thread. Let the worker release the port.
[NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:)
toTarget:[MyWorkerClass class] withObject:myPort];
}
}
為了在線程之間建立一個雙向通信通道敢艰,您可能希望工作線程在登錄消息中將自己的本地端口發(fā)送到主線程。 接收登錄消息讓您的主線程知道在啟動第二個線程時一切順利册赛,并且還可以向您發(fā)送更多消息到該線程钠导。
下面代碼顯示了主線程的 handlePortMessage:
方法震嫉。 當(dāng)數(shù)據(jù)到達(dá)線程自己的本地端口時調(diào)用此方法。 當(dāng)一個登錄消息到達(dá)時牡属,該方法直接從端口消息中檢索次要線程的端口票堵,并保存以備以后使用。
// Handling Mach port messages
#define kCheckinMessage 100
// Handle responses from the worker thread.
- (void)handlePortMessage:(NSPortMessage *)portMessage
{
unsigned int message = [portMessage msgid];
NSPort* distantPort = nil;
if (message == kCheckinMessage)
{
// Get the worker thread’s communications port.
distantPort = [portMessage sendPort];
// Retain and save the worker port for later use.
[self storeDistantPort:distantPort];
}
else
{
// Handle other messages.
}
}
Implementing the Secondary Thread Code
對于輔助工作線程逮栅,您必須配置線程并使用指定的端口將信息傳回主線程悴势。
下面代碼顯示了設(shè)置工作線程的代碼。 為線程創(chuàng)建自動釋放池后措伐,該方法將創(chuàng)建一個工作對象來驅(qū)動線程執(zhí)行特纤。 工作對象的sendCheckinMessage:
方法(如下面第二端代碼所示)為工作線程創(chuàng)建一個本地端口,并將一個簽入消息發(fā)送回主線程侥加。
//Launching the worker thread using Mach ports
+(void)LaunchThreadWithPort:(id)inData
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Set up the connection between this thread and the main thread.
NSPort* distantPort = (NSPort*)inData;
MyWorkerClass* workerObj = [[self alloc] init];
[workerObj sendCheckinMessage:distantPort];
[distantPort release];
// Let the run loop process things.
do
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
while (![workerObj shouldExit]);
[workerObj release];
[pool release];
}
當(dāng)使用NSMachPort
時捧存,本地和遠(yuǎn)程線程可以使用相同的端口對象進(jìn)行線程之間的單向通信。 換句話說担败,由一個線程創(chuàng)建的本地端口對象將成為另一個線程的遠(yuǎn)程端口對象昔穴。
下面代碼中顯示了次要線程的簽入例程。 該方法設(shè)置自己的本地端口用于將來的通信提前,然后發(fā)送一個檢入消息回主線程吗货。 該方法使用在LaunchThreadWithPort:
方法中接收的端口對象作為消息的目標(biāo)。
// Sending the check-in message using Mach ports
// Worker thread check-in method
- (void)sendCheckinMessage:(NSPort*)outPort
{
// Retain and save the remote port for future use.
[self setRemotePort:outPort];
// Create and configure the worker thread port.
NSPort* myPort = [NSMachPort port];
[myPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
// Create the check-in message.
NSPortMessage* messageObj = [[NSPortMessage alloc] initWithSendPort:outPort receivePort:myPort components:nil];
if (messageObj)
{
// Finish configuring the message and send it immediately.
[messageObj setMsgId:setMsgid:kCheckinMessage];
[messageObj sendBeforeDate:[NSDate date]];
}
}
2. Configuring an NSMessagePort Object - NSMessagePort對象的配置
要建立與NSMessagePort對象的本地連接狈网,您不能簡單地在線程之間傳遞端口對象宙搬。 遠(yuǎn)程消息端口必須以名稱獲取。 在Cocoa
中可能需要使用特定的名稱注冊本地端口孙援,然后將該名稱傳遞給遠(yuǎn)程線程害淤,以便它可以獲取適當(dāng)?shù)亩丝趯ο筮M(jìn)行通信。 下面代碼顯示了要使用消息端口的端口創(chuàng)建和注冊過程拓售。
//Registering a message port
NSPort* localPort = [[NSMessagePort alloc] init];
// Configure the object and add it to the current run loop.
[localPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:localPort forMode:NSDefaultRunLoopMode];
// Register the port using a specific name. The name must be unique.
NSString* localPortName = [NSString stringWithFormat:@"MyPortName"];
[[NSMessagePortNameServer sharedInstance] registerPort:localPort name:localPortName];
3. Configuring a Port-Based Input Source in Core Foundation - 在Core Foundation中配置基于端口的輸入源
本節(jié)介紹如何使用Core Foundation在應(yīng)用程序的主線程和工作線程之間設(shè)置雙向通信通道窥摄。
下面代碼顯示了應(yīng)用程序主線程調(diào)用的代碼,以啟動工作線程础淤。 代碼的第一件事是設(shè)置一個CFMessagePortRef opaque類型來監(jiān)聽來自工作線程的消息崭放。 工作線程需要端口名稱進(jìn)行連接,以便將字符串值傳遞給工作線程的入口點(diǎn)函數(shù)鸽凶。 端口名稱通常在當(dāng)前用戶上下文中是唯一的; 否則币砂,您可能會遇到?jīng)_突。
//Attaching a Core Foundation message port to a new
thread
#define kThreadStackSize (8 *4096)
OSStatus MySpawnThread()
{
// Create a local port for receiving responses.
CFStringRef myPortName;
CFMessagePortRef myPort;
CFRunLoopSourceRef rlSource;
CFMessagePortContext context = {0, NULL, NULL, NULL, NULL};
Boolean shouldFreeInfo;
// Create a string with the port name.
myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.myapp.MainThread"));
// Create the port.
myPort = CFMessagePortCreateLocal(NULL,
myPortName,
&MainThreadResponseHandler,
&context,
&shouldFreeInfo);
if (myPort != NULL)
{
// The port was successfully created.
// Now create a run loop source for it.
rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
if (rlSource)
{
// Add the source to the current run loop.
CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
// Once installed, these can be freed.
CFRelease(myPort);
CFRelease(rlSource);
}
}
// Create the thread and continue processing.
MPTaskID taskID;
return(MPCreateTask(&ServerThreadEntryPoint,
(void*)myPortName,
kThreadStackSize,
NULL,
NULL,
NULL,
0,
&taskID));
}
在安裝端口并啟動線程的情況下玻侥,主線程可以在等待線程檢入時繼續(xù)其正常執(zhí)行决摧。當(dāng)檢入消息到達(dá)時,它將被分派到主線程的MainThreadResponseHandler
函數(shù),下面代碼顯示的是此函數(shù)提取工作線程的端口名稱掌桩,并創(chuàng)建未來通信的管道边锁。
//Receiving the checkin message
#define kCheckinMessage 100
// Main thread port message handler
CFDataRef MainThreadResponseHandler(CFMessagePortRef local,
SInt32 msgid,
CFDataRef data,
void* info)
{
if (msgid == kCheckinMessage)
{
CFMessagePortRef messagePort;
CFStringRef threadPortName;
CFIndex bufferLength = CFDataGetLength(data);
UInt8* buffer = CFAllocatorAllocate(NULL, bufferLength, 0);
CFDataGetBytes(data, CFRangeMake(0, bufferLength), buffer);
threadPortName = CFStringCreateWithBytes (NULL, buffer, bufferLength, kCFStringEncodingASCII, FALSE);
// You must obtain a remote message port by name.
messagePort = CFMessagePortCreateRemote(NULL, (CFStringRef)threadPortName);
if (messagePort)
{
// Retain and save the thread’s comm port for future reference.
AddPortToListOfActiveThreads(messagePort);
// Since the port is retained by the previous function, release
// it here.
CFRelease(messagePort);
}
// Clean up.
CFRelease(threadPortName);
CFAllocatorDeallocate(NULL, buffer);
}
else
{
// Process other messages.
}
return NULL;
}
在配置主線程之后,唯一剩下的就是新創(chuàng)建的工作線程創(chuàng)建自己的端口并簽入波岛。下面代碼顯示了工作線程的入口點(diǎn)函數(shù)茅坛。 該函數(shù)提取主線程的端口名稱,并使用它來創(chuàng)建一個遠(yuǎn)程連接回主線程则拷。 該函數(shù)然后為其自身創(chuàng)建本地端口贡蓖,將端口安裝在線程的運(yùn)行循環(huán)上,并向包含本地端口名稱的主線程發(fā)送簽入消息煌茬。
//Setting up the thread structures
OSStatus ServerThreadEntryPoint(void* param)
{
// Create the remote port to the main thread.
CFMessagePortRef mainThreadPort;
CFStringRef portName = (CFStringRef)param;
mainThreadPort = CFMessagePortCreateRemote(NULL, portName);
// Free the string that was passed in param.
CFRelease(portName);
// Create a port for the worker thread.
CFStringRef myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.MyApp.Thread-%d"), MPCurrentTaskID());
// Store the port in this thread’s context info for later reference.
CFMessagePortContext context = {0, mainThreadPort, NULL, NULL, NULL};
Boolean shouldFreeInfo;
Boolean shouldAbort = TRUE;
CFMessagePortRef myPort = CFMessagePortCreateLocal(NULL,
myPortName,
&ProcessClientRequest,
&context,
&shouldFreeInfo);
if (shouldFreeInfo)
{
// Couldn't create a local port, so kill the thread.
MPExit(0);
}
CFRunLoopSourceRef rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
if (!rlSource)
{
// Couldn't create a local port, so kill the thread.
MPExit(0);
}
// Add the source to the current run loop.
CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
// Once installed, these can be freed.
CFRelease(myPort);
CFRelease(rlSource);
// Package up the port name and send the check-in message.
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);
// Clean up thread data structures.
CFRelease(outData);
CFAllocatorDeallocate(NULL, buffer);
// Enter the run loop.
CFRunLoopRun();
}
一旦進(jìn)入其運(yùn)行循環(huán)斥铺,發(fā)送到線程端口的所有未來事件都將由ProcessClientRequest
函數(shù)處理。 該功能的實(shí)現(xiàn)取決于線程工作的類型宣旱。
后記
未完仅父,待續(xù)~~