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

版本記錄

版本號 時間
V1.0 2017.08.24

前言

NSRunloopOC 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ì)時器:

這些方法創(chuàng)建定時器,并以默認(rèn)模式(NSDefaultRunLoopMode)將其添加到當(dāng)前線程的運(yùn)行循環(huán)中欣舵。 你也可以手動調(diào)度計(jì)時器擎鸠,如果您想通過創(chuàng)建NSTimer對象然后使用NSRunLoopaddTimer: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的輸入源

CocoaCore 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ù)~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市浑吟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌耗溜,老刑警劉巖组力,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異抖拴,居然都是意外死亡燎字,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門阿宅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來候衍,“玉大人,你說我怎么就攤上這事洒放◎嚷梗” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵往湿,是天一觀的道長妖异。 經(jīng)常有香客問我,道長领追,這世上最難降的妖魔是什么他膳? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮绒窑,結(jié)果婚禮上棕孙,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好蟀俊,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布钦铺。 她就那樣靜靜地躺著,像睡著了一般欧漱。 火紅的嫁衣襯著肌膚如雪职抡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天误甚,我揣著相機(jī)與錄音缚甩,去河邊找鬼。 笑死窑邦,一個胖子當(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
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乍炉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年绢片,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岛琼。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡底循,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出槐瑞,到底是詐尸還是另有隱情熙涤,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布随珠,位于F島的核電站灭袁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏窗看。R本人自食惡果不足惜茸歧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望显沈。 院中可真熱鬧软瞎,春花似錦逢唤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至只锭,卻和暖如春著恩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜻展。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工喉誊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纵顾。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓伍茄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親施逾。 傳聞我的和親對象是個殘疾皇子敷矫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

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