Run loops是線(xiàn)程相關(guān)的一些基本東西。一個(gè)run loop是一個(gè)處理消息的循環(huán)。用來(lái)處理計(jì)劃任務(wù)或者收到的事件。run loop的作用是在有事做的時(shí)候保持線(xiàn)程繁忙,沒(méi)事的時(shí)候讓線(xiàn)程掛起。
Run loop的管理并不是完全自動(dòng)的。你仍然需要設(shè)計(jì)代碼來(lái)在合適的時(shí)候啟動(dòng)run loop來(lái)相應(yīng)事件。Cocoa和Core Foundation都提供了run loop對(duì)象來(lái)配置和管理run loop。程序并不需要?jiǎng)?chuàng)建這些對(duì)象,每個(gè)線(xiàn)程,包括主線(xiàn)程都有一個(gè)對(duì)應(yīng)的run loop對(duì)象。只有非主線(xiàn)程需要明確的啟動(dòng)它的run loop。自動(dòng)啟動(dòng)主線(xiàn)程的run loop是app框架啟動(dòng)流程的一部分。
下面會(huì)介紹一下run loop以及如何配置它。
?
Run Loop 詳解
Run loop正如它名字所說(shuō)的一樣。是線(xiàn)程進(jìn)入的一個(gè)環(huán),用來(lái)處理接收和處理事件。你需要寫(xiě)代碼來(lái)控制run loop實(shí)際的循環(huán),也就是說(shuō),你需要提供驅(qū)動(dòng)run loop的while或者for循環(huán)。在循環(huán)中,使用run loop對(duì)象來(lái)處理事件,接收事件以及調(diào)用對(duì)應(yīng)的處理程序。
run loop接收兩種源。輸入源傳遞異步的消息,通常是其他線(xiàn)程或其他程序發(fā)送過(guò)來(lái)的。定時(shí)器源傳遞同步事件,在一個(gè)計(jì)劃的時(shí)間或重復(fù)的時(shí)間間隔產(chǎn)生。兩種類(lèi)型都使用程序指定的處理程序來(lái)處理事件。
下面的圖展示了run loop和它的消息源的概念。輸入源傳遞異步事件給對(duì)應(yīng)的處理程序并且導(dǎo)致runUntilDate:方法退出。Timer發(fā)送事件給對(duì)應(yīng)的處理程序但是不會(huì)導(dǎo)致run loop退出
?
另外,run loop有時(shí)候會(huì)發(fā)出廣播。注冊(cè)run-loop observers可以接收到這些廣播然后來(lái)在線(xiàn)程上做你想做的事。可以使用Core Foundation來(lái)在線(xiàn)程上設(shè)置run-loop observers
?
Run Loop Modes
一個(gè)run loop mode是一個(gè)需要監(jiān)控和處理的輸入源和定時(shí)器的集合。每次運(yùn)行run loop,都可以設(shè)置一個(gè)類(lèi)型來(lái)運(yùn)行。在這種情況下,只有和這種類(lèi)型相關(guān)的事件才會(huì)被接收到。(也就是說(shuō),只有和這種類(lèi)型相關(guān)的事件才會(huì)通知run loop的執(zhí)行程序。)其他類(lèi)型相關(guān)的源會(huì)掛起直到有對(duì)應(yīng)的類(lèi)型來(lái)接收它。
代碼中使用名字來(lái)標(biāo)識(shí)類(lèi)型。Cocoa和Core Foundation都定義了一個(gè)默認(rèn)類(lèi)型以及其他幾個(gè)通常用到的類(lèi)型,也是通過(guò)字符串來(lái)標(biāo)識(shí)他們的。你可以給類(lèi)型名字指定一個(gè)字符串來(lái)自定義類(lèi)型。雖然你可以自定義任何名字,但是類(lèi)型的內(nèi)容并不是任意的。你必須要添加一個(gè)或多個(gè)輸入源,定時(shí)器或run-loop observer來(lái)讓他們有意義。
使用類(lèi)型來(lái)過(guò)濾run loop的事件。大多數(shù)時(shí)候,都會(huì)運(yùn)行系統(tǒng)默認(rèn)的類(lèi)型。modal panel可能會(huì)使用"modal"類(lèi)型。在這種類(lèi)型下,只有和modal pannel相關(guān)的事件會(huì)發(fā)送。對(duì)于其他線(xiàn)程,可以使用自定義的類(lèi)型來(lái)過(guò)濾低優(yōu)先級(jí)的源。
提示:類(lèi)型是根據(jù)事件源的類(lèi)型,而不是事件的類(lèi)型。比如,你不僅僅需要只需要鼠標(biāo)按下或鍵盤(pán)事件。可能還需要監(jiān)聽(tīng)端口,定時(shí)器掛起,或源的改變等。
下面是Cocoa和Core Foundation預(yù)定義的一些類(lèi)型。
Mode | 名字 | 描述 |
Defaule |
NSDefaultRunLoopMode(Cocoa) kCFRunLoopDefaultMode(Core Foundation) |
默認(rèn)類(lèi)型是大部分操作用到的。大多數(shù)時(shí)候都使用這種類(lèi)型來(lái)啟動(dòng)run loop以及配置輸入源。 |
Connection | NSConnectionReplyMode(Cocoa) |
Cocoa使用這種類(lèi)型來(lái)監(jiān)測(cè)NSCOnnection對(duì)象返回。很少用到這種類(lèi)型。 |
Modal | NSModalPannelRunLoopMode(Cocoa) |
Cocoa使用這種類(lèi)型來(lái)標(biāo)識(shí)modal pannels相關(guān)的事件。 |
Event tracking | NSEventTrackingRunLoopMode(Cocoa) |
Cocoa使用這種類(lèi)型來(lái)監(jiān)測(cè)鼠標(biāo)拖動(dòng)以及其他類(lèi)型的用戶(hù)界面操作追蹤。 |
Common modes |
NSRunLoopCommonModes(Cocoa) kCFRunLoopCommonModes(Core Foundation) |
這是常用類(lèi)型的集合。指定一個(gè)輸入源和這個(gè)類(lèi)型相關(guān)也就是指定它和這個(gè)集合的類(lèi)型相關(guān)。對(duì)于Cocoa程序,這個(gè)集合包括default,modal,以及event tracking類(lèi)型。Core Foundation只包括default類(lèi)型。可以使用CFRunLoopAddCommonMode方法來(lái)添加自定義類(lèi)型到這個(gè)集合 |
?
輸入源
輸入源異步的發(fā)送消息到線(xiàn)程。事件的源取決于輸入源,總體上有兩種類(lèi)型。基于Port的源模擬程序的port源,自定義源模擬自定義事件。不過(guò)run loop關(guān)心的并不是基于port或自定義事件。系統(tǒng)基本上兩種都會(huì)實(shí)現(xiàn)。唯一的不同是他們?nèi)绾伟l(fā)出的。基于port的是自動(dòng)由內(nèi)核發(fā)出的,自定義的源是由其他線(xiàn)程發(fā)出的。
創(chuàng)建輸入源時(shí),可以指定一個(gè)或多個(gè)run loop類(lèi)型。類(lèi)型決定了輸入源在什么時(shí)候被監(jiān)測(cè)到。大多數(shù)時(shí)候,run loop在默認(rèn)類(lèi)型下運(yùn)行,不過(guò)也可以指定類(lèi)型。如果輸入源不是當(dāng)前監(jiān)測(cè)的類(lèi)型,任何產(chǎn)生的事件都會(huì)掛起,直到有對(duì)應(yīng)的類(lèi)型能接收它。
下面介紹一些輸入源
?
Port-Based Sources
Cocoa和Core Foundation提供了創(chuàng)建port-based源的相應(yīng)對(duì)象和方法。例如,在Cocoa,基本上不用直接創(chuàng)建輸入源。只需要?jiǎng)?chuàng)建一個(gè)port對(duì)象然后使用NSPort的方法把它加到run loop中。port對(duì)象會(huì)處理創(chuàng)建和配置輸入源的事情。
?
自定義輸入源
創(chuàng)建自定義輸入源,需要使用Core Foundation中CFRunLoopSourceRef相關(guān)的方法。可以給輸入源配置幾個(gè)回調(diào)方法。Core Foundation會(huì)在不同點(diǎn)回調(diào)這些方法來(lái)配置輸入源,處理事件,以及在從run loop移除時(shí)銷(xiāo)毀它。
另外如果要定義收到事件的行為的話(huà),需要定義事件的分發(fā)機(jī)制。這部分在另一個(gè)線(xiàn)程上運(yùn)行,它負(fù)責(zé)給輸入源提供數(shù)據(jù)并且在數(shù)據(jù)準(zhǔn)備好后通知它。事件的分發(fā)之際又你決定,但是不要弄的太復(fù)雜。
?
Cocoa Perform Selector Sources
對(duì)于port-based sources,Cocoa定義了一個(gè)自定義的源來(lái)在任何線(xiàn)程上執(zhí)行方法。和port-based源相似的是,執(zhí)行方法的請(qǐng)求在目標(biāo)線(xiàn)程上被序列化,這樣可以減輕多個(gè)方法在同一個(gè)線(xiàn)程上被調(diào)用的同步問(wèn)題。和port-based源不同的是,它執(zhí)行后會(huì)從run loop把自己移除掉。
在另一個(gè)線(xiàn)程上執(zhí)行方法時(shí),目標(biāo)線(xiàn)程必須要有一個(gè)活著的run loop。對(duì)于你創(chuàng)建的線(xiàn)程,這意味著它會(huì)等到啟動(dòng)run loop時(shí)才執(zhí)行。因?yàn)橹骶€(xiàn)程會(huì)自動(dòng)啟動(dòng)它的run loop,因此在程序調(diào)用applicationDidFinishLaunching:后就可以開(kāi)始調(diào)用這個(gè)方法了。run loop會(huì)一次調(diào)用所有計(jì)劃的方法,而不是一個(gè)循環(huán)調(diào)用一個(gè)。
下面列出了NSObject在另一個(gè)線(xiàn)程上執(zhí)行方法的方法。由于是在NSObject中定義的,所以可以在任何能訪(fǎng)問(wèn)Objective-C對(duì)象的線(xiàn)程中調(diào)用,包括POSIX線(xiàn)程。這些方法不會(huì)創(chuàng)建新線(xiàn)程來(lái)執(zhí)行他們。
方法 | 描述 |
performSelectorOnMainThread:withObject:waitUntilDone: performSelectorOnMainThread:withObject:waitUntilDone:modes: |
在主線(xiàn)程的下一個(gè)run loop中執(zhí)行指定的方法。這個(gè)方法可以阻塞當(dāng)前線(xiàn)程直到指定的方法執(zhí)行完 |
performSelector:onThread:withObject:waitUntilDone: performSelector:onThread:withObject:waitUntilDone:modes: |
在指定的線(xiàn)程上執(zhí)行指定的方法。可以阻塞房前線(xiàn)程直到指定的方法執(zhí)行完 |
performSelector:withObject:afterDelay: performSelector:withObject:afterDelay:inModes: |
在當(dāng)前線(xiàn)程的下一個(gè)run loop中執(zhí)行指定方法并且可以設(shè)置延時(shí)時(shí)間。由于等到下個(gè)run loop才會(huì)執(zhí)行,所以這些方法默認(rèn)的有一個(gè)最小的延時(shí)時(shí)間。隊(duì)列中的多個(gè)方法會(huì)根據(jù)隊(duì)列中的位置一個(gè)一個(gè)的執(zhí)行。 |
cancelPreviousPerformRequestsWithTarget: cancelPreviousPerformRequestsWithTarget:selector:object: |
可以取消使用 performSelector:withObject:afterDelay: 或 performSelector:withObject:afterDelay:inModes: 發(fā)送給當(dāng)前線(xiàn)程的消息。 |
?
定時(shí)器源
定時(shí)器源會(huì)在預(yù)設(shè)的時(shí)間給線(xiàn)程同步的發(fā)送事件。定時(shí)器是線(xiàn)程通知自己做一些事情的一種方法。例如,搜索框可以用定時(shí)器來(lái)實(shí)現(xiàn)用戶(hù)輸入內(nèi)容后自動(dòng)搜索。延時(shí)可以讓用戶(hù)在開(kāi)始搜索前輸入想輸入的內(nèi)容。
雖然它產(chǎn)生基于時(shí)間的消息,定時(shí)器并不是實(shí)時(shí)的。和輸入源一樣,定時(shí)器也是和run loop指定的類(lèi)型相關(guān)。如果定時(shí)器不在當(dāng)前run loop監(jiān)測(cè)的類(lèi)型中,它會(huì)一直等到支持定時(shí)器的run loop執(zhí)行時(shí)才會(huì)觸發(fā)。同樣的,如果定時(shí)器在run loop執(zhí)行的過(guò)程中觸發(fā)了,定時(shí)器會(huì)等到下一個(gè)run loop才能實(shí)行相應(yīng)的方法。如果run loop沒(méi)有運(yùn)行,定時(shí)器就根本不會(huì)觸發(fā)。
?
Run Loop Observers
和源不同,源是在同步或異步事件產(chǎn)生是觸發(fā),run loop observers在run loop指定的特殊點(diǎn)觸發(fā)。可以使用run loop observsers來(lái)讓線(xiàn)程準(zhǔn)備處理事件或讓線(xiàn)程準(zhǔn)備掛起。可以把run loop observers和下面的事件關(guān)聯(lián)起來(lái):
- run loop的入口
- run loop將要開(kāi)始執(zhí)行定時(shí)器
- run loop將要執(zhí)行一個(gè)輸入源
- run loop將要掛起
- run loop被喚醒
- run loop退出
可以使用Core Foundation來(lái)添加run loop監(jiān)聽(tīng)者。要?jiǎng)?chuàng)建一個(gè)run loop監(jiān)聽(tīng)者,需要?jiǎng)?chuàng)建一個(gè)CFRunLoopObserverRef實(shí)例。這個(gè)類(lèi)型會(huì)追蹤指定的回調(diào)方法以及感興趣的事件。
和定時(shí)器相似,run loop監(jiān)聽(tīng)者可以被使用一次或重復(fù)使用。一次性的監(jiān)聽(tīng)者會(huì)在觸發(fā)后從run loop移除,重復(fù)使用的會(huì)保留。使用一次還是重復(fù)使用是在創(chuàng)建時(shí)指定的。
?
Run Loop事件順序
每次運(yùn)行時(shí),線(xiàn)程的run loop會(huì)執(zhí)行預(yù)定義的事件并且給每個(gè)監(jiān)聽(tīng)者發(fā)廣播。調(diào)用的順序時(shí)固定的:
- 通知監(jiān)聽(tīng)者run loop進(jìn)入了
- 通知監(jiān)聽(tīng)者任何準(zhǔn)備好的定時(shí)器將要觸發(fā)
- 通知監(jiān)聽(tīng)者任何非基于port的輸入源將要觸發(fā)
- 觸發(fā)任何非基于port的事件
- 如果有任何基于port事件將要觸發(fā),處理事件,然后到第9步
- 通知監(jiān)聽(tīng)者線(xiàn)程將要掛起
-
把線(xiàn)程掛起直到下面的事件之一觸發(fā)
- 一個(gè)基于port的消息觸發(fā)
- 定義定時(shí)器觸發(fā)
- run loop設(shè)置的超時(shí)時(shí)間到了
- run loop被明確的喚醒
- 通知監(jiān)聽(tīng)者線(xiàn)程被喚醒
-
處理需要處理的事件
- 如果一個(gè)用戶(hù)定義的定時(shí)器觸發(fā),執(zhí)行定時(shí)器然后重啟消息循環(huán)。跳轉(zhuǎn)到第2步
- 如果一個(gè)事件源觸發(fā),分發(fā)事件
- 如果run loop被明確的喚醒但是還沒(méi)有超時(shí),重啟消息循環(huán)。跳轉(zhuǎn)到第2步
- 通知所有的監(jiān)聽(tīng)者run loop退出
由于定時(shí)器和輸入源的監(jiān)聽(tīng)者廣播是在事件執(zhí)行前發(fā)出的,廣播的事件和真實(shí)執(zhí)行的事件可能會(huì)有時(shí)間差。如果這個(gè)時(shí)間差很重要,可以使用sleep和awake-from-sleep廣播來(lái)修正真實(shí)時(shí)間。
由于定時(shí)器和其他周期性的事件是在run loop運(yùn)行時(shí)分發(fā)的,破壞run loop會(huì)破會(huì)消息分發(fā)。
?
什么時(shí)候應(yīng)該使用Run Loop
程序中只有明確的需要使用另外一個(gè)線(xiàn)程時(shí)才會(huì)需要run loop。主線(xiàn)程的run loop是程序的基礎(chǔ)部分之一。所以,app的框架提供了運(yùn)行主線(xiàn)程以及自動(dòng)啟動(dòng)run loop的代碼。UIApplication的run方法啟動(dòng)了主循環(huán)。如果使用Xcode工程模版創(chuàng)建程序,根本步需要調(diào)用這些方法。
對(duì)于輔助線(xiàn)程,你需要決定是否需要run loop, 如果需要的話(huà)需要自己配置并啟動(dòng)它。有些時(shí)候完全步需要run loop。比如,創(chuàng)建一個(gè)線(xiàn)程來(lái)執(zhí)行一個(gè)長(zhǎng)時(shí)間或指定好的任務(wù),這時(shí)候根本不需要啟動(dòng)run loop。Run loop在需要很多線(xiàn)程間交互的時(shí)候使用。比如,需要做下面的事情時(shí):
- 使用基于port或自定義的源來(lái)進(jìn)行線(xiàn)程間通訊
- 在線(xiàn)程上使用定時(shí)器
- 在Cocoa程序中使用performSelector...方法
- 執(zhí)行周期性的任務(wù)
如果選擇使用run loop,配置和使用是相對(duì)簡(jiǎn)單的。在整個(gè)多線(xiàn)程編程過(guò)程中,最好是計(jì)劃好需要哪些輔助線(xiàn)程。線(xiàn)程最好是讓它正常結(jié)束而不是強(qiáng)行中止。
?
使用Run Loop對(duì)象
run loop對(duì)象提供了添加輸入源,定時(shí)器,run-loop obervers以及運(yùn)行它的接口。每個(gè)線(xiàn)程都有一個(gè)對(duì)應(yīng)的run loop對(duì)象。在Cocoa中,這個(gè)對(duì)象是一個(gè)NSRunLoop類(lèi)實(shí)例。在底層程序中,它是指向CFRunLoopRef的指針。
?
獲取Run Loop對(duì)象
要獲取當(dāng)前線(xiàn)程的run loop對(duì)象,可以使用下面的方法:
- 在Cocoa程序中,使用NSRunLoop的currentRunLoop方法來(lái)獲得NSRunLoop對(duì)象。
- 使用CFRunLoopGetCurrent方法。
雖然他們不是完全相同的,但是也可以通過(guò)NSRunLoop對(duì)象獲得CFRunLoopRef。NSRunLoop類(lèi)定義了一個(gè)getCGRunLoop方法來(lái)返回一個(gè)CGRunLoopRef類(lèi)型。由于兩種對(duì)象指向同一個(gè)run loop,因此NSRunLoop對(duì)象和CFRunLoopRef可以混合使用。
?
配置Run Loop
在輔助線(xiàn)程中使用run loop之前,需要至少加入一個(gè)輸入源或定時(shí)器。如果run loop沒(méi)有東西要監(jiān)控,它運(yùn)行時(shí)會(huì)立刻退出。
除了添加源以外,也可以添加run loop監(jiān)聽(tīng)者來(lái)監(jiān)測(cè)run loop運(yùn)行的狀態(tài)。創(chuàng)建run loop監(jiān)聽(tīng)者,需要?jiǎng)?chuàng)建一個(gè)CFRunLoopObserverRef類(lèi)型,然后使用CGRunLoopAddObserver方法添加到run loop。就算是Cocoa程序,run loop監(jiān)聽(tīng)者也需要使用Core Foundation來(lái)創(chuàng)建。
下面展示了創(chuàng)建run loop監(jiān)聽(tīng)者的主要程序。
- ( 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); }
如果是配置長(zhǎng)時(shí)間存活的線(xiàn)程,最好是添加一個(gè)輸入源來(lái)接收消息。雖然可以只添加一個(gè)定時(shí)器,一旦定時(shí)器觸發(fā)后,如果定時(shí)器無(wú)效了,就會(huì)導(dǎo)致run loop退出。添加重復(fù)的定時(shí)器可以讓run loop一直運(yùn)行,但是會(huì)周期性的喚醒線(xiàn)程。相對(duì)來(lái)說(shuō),輸入源會(huì)等待事件發(fā)生,直到事件發(fā)生時(shí)才喚醒線(xiàn)程。
?
啟動(dòng)Run Loop
只有輔助線(xiàn)程才需要我們啟動(dòng)run loop。run loop必須要有一個(gè)輸入源或定時(shí)器。如果沒(méi)有,run loop會(huì)立刻退出。
有幾種方式啟動(dòng)run loop,包括:
- 無(wú)條件的
- 設(shè)置一個(gè)時(shí)間限制
- 指定一個(gè)類(lèi)型
無(wú)條件的進(jìn)入run loop是最簡(jiǎn)單的,同時(shí)也是最不推薦的。無(wú)條件的啟動(dòng)run loop會(huì)讓線(xiàn)程進(jìn)入一個(gè)死循環(huán),會(huì)讓你基本無(wú)法控制run loop。可以添加刪除輸入源和定時(shí)器,但是想要停止run loop的方法只能強(qiáng)行殺掉它。
相對(duì)于無(wú)條件的運(yùn)行,更好的方式是設(shè)置一個(gè)時(shí)間限制。設(shè)置一個(gè)時(shí)間限制后,run loop會(huì)運(yùn)行到有事件觸發(fā)活著到達(dá)設(shè)置的時(shí)間。如果有事件觸發(fā),會(huì)分發(fā)事件然后退出run loop。你可以在代碼中重啟run loop來(lái)等待下一個(gè)事件。如果設(shè)置的時(shí)間到了,可以見(jiàn)到的重啟run loop或使用這個(gè)時(shí)間做點(diǎn)其他事。
除了設(shè)置時(shí)間限制外,也可以給run loop指定一種類(lèi)型。類(lèi)型和超時(shí)并不是互斥的,他們可以同時(shí)被使用。類(lèi)型用來(lái)限制輸入源的類(lèi)型。
下面展示一個(gè)線(xiàn)程的入口框架。主要是添加輸入源和定時(shí)器后,重復(fù)的調(diào)用run loop來(lái)接收消息。每次run loop返回時(shí),查看是否到達(dá)了結(jié)束的條件。
- ( void )skeletonThreadMain { // Set up an autorelease pool here if not using garbage collection. BOOL done = NO; // Add your sources or timers to the run loop and do any other setup. do { // Start the run loop but return after each source is handled. SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10 , YES); // If a source explicitly stopped the run loop, or if there are no // sources or timers, go ahead and exit. if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished)) done = YES; // Check for any other exit conditions here and set the // done variable as needed. } while (! done); // Clean up code here. Be sure to release any allocated autorelease pools. }
run loop是可以遞歸的使用的。也就是說(shuō),可以在輸入源或定時(shí)器的處理程序中調(diào)用CFRunLoopRun,CFRunLoopRunInMode等方法。
?
退出Run Loop
有兩種方式可以讓run loop收到事件前退出
- 配置run loop的超時(shí)時(shí)間
- 主動(dòng)讓run loop停止
如果可以的話(huà),最好是使用超時(shí)時(shí)間。指定超時(shí)時(shí)間可以讓run loop在退出前完成它所有應(yīng)該做的事,包括給監(jiān)聽(tīng)者發(fā)送廣播。
明確的使用CFRunLoopStop停止run loop和超時(shí)相似。run loop也會(huì)把需要發(fā)送的廣播發(fā)送給監(jiān)聽(tīng)者。不同的事這種方法主要用在使用無(wú)條件啟動(dòng)的run loop。
雖然刪除Run Loop的輸入源和定時(shí)器也會(huì)導(dǎo)致run loop退出,但是不是很靠譜。系統(tǒng)也許會(huì)自動(dòng)添加一些輸入源。可能代碼沒(méi)有意識(shí)到這些輸入源,他們可能是無(wú)法移除的,這會(huì)導(dǎo)致run loop無(wú)法退出。
?
線(xiàn)程安全以及Run Loop對(duì)象
線(xiàn)程安全取決于你使用什么API來(lái)維護(hù)run loop。Core Foundation中的方法整體上都是線(xiàn)程安全的并且可以在任何線(xiàn)程調(diào)用。如果是做改變r(jià)un loop的配置的操作,最好還是在run loop所屬的線(xiàn)程做比較好。
Cocoa的NSRunLoop類(lèi)并沒(méi)有繼承Core Foundation的線(xiàn)程安全部分。如果是使用NSRunLoop類(lèi)來(lái)修改run loop,應(yīng)該在run loop所屬的線(xiàn)程上做。在另一個(gè)線(xiàn)程上給run loop添加輸入源或定時(shí)器可能會(huì)導(dǎo)致crash或其他異常。
?
配置Run Loop源
自定義輸入源
創(chuàng)建自定義源包括下面一些內(nèi)容:
- 輸入源需要執(zhí)行的信息
- 感興趣的客戶(hù)如何與輸入源交互
- 處理客戶(hù)請(qǐng)求的處理程序
- 取消輸入源的方法
- 由于是自定義的輸入源來(lái)處理自定義的信息,所有實(shí)際的配置就很靈活了。調(diào)度,處理,取消流程是自定義源的主要流程。大多數(shù)其他行為都在這幾個(gè)方法之外。比如,什么時(shí)候傳遞數(shù)據(jù)什么時(shí)候和其他線(xiàn)程交互由你決定。
下面的圖展示了一個(gè)簡(jiǎn)單的自定義源。程序的主線(xiàn)程維護(hù)了一個(gè)輸入源的引用,輸入源的命令緩沖區(qū),以及輸入源所在的run loop。當(dāng)主線(xiàn)程有工作交給工作線(xiàn)程時(shí),它把命令和所需的數(shù)據(jù)一起發(fā)到命令緩沖區(qū)來(lái)讓工作線(xiàn)程開(kāi)始工作。(由于主線(xiàn)程和工作線(xiàn)程都能訪(fǎng)問(wèn)命令緩沖區(qū),所以訪(fǎng)問(wèn)必須是同步的。)命令發(fā)送之后,主線(xiàn)程會(huì)給輸入源發(fā)送一個(gè)信號(hào)來(lái)喚醒工作線(xiàn)程的run loop。當(dāng)收到喚醒命令后,run loop調(diào)用輸入源的處理程序來(lái)處理命令緩沖區(qū)中的命令。
?
定義輸入源
定義輸入源需要使用Core Foundation來(lái)配置以及和run loop交互。雖然基本的處理程序是基于C的方法,也可以使用Objective-C或C++來(lái)封裝。
下面展示了一個(gè)輸入源的定義。RunLoopSource對(duì)象管理一個(gè)命令緩沖區(qū)并且用它來(lái)接收其他線(xiàn)程的消息。也展示了一個(gè)RunLoopContext對(duì)象的定義,它只是一個(gè)把RunLoopSource對(duì)象和run loop對(duì)象的引用傳遞給主線(xiàn)程的容器。
@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
雖然使用Objectice-C代碼管理輸入源的自定義數(shù)據(jù),把輸入源和run loop關(guān)聯(lián)仍然需要c回調(diào)方法。第一個(gè)被調(diào)用的方法是把它和run loop關(guān)聯(lián)起來(lái)。由于這個(gè)輸入源只有一個(gè)客戶(hù)端(主線(xiàn)程),它調(diào)用計(jì)劃方來(lái)發(fā)送消息來(lái)注冊(cè)到線(xiàn)程上。當(dāng)代理需要和輸入源通訊時(shí),使用RunLoopContext對(duì)象中的信息就可以了。
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)過(guò)程之一是,當(dāng)輸入源觸發(fā)時(shí)處理自定義的數(shù)據(jù)。下面展示了處理RunLoopSource對(duì)象相關(guān)的回調(diào)。這個(gè)方法只是簡(jiǎn)單的把請(qǐng)求轉(zhuǎn)發(fā)給sourceFired方法,它會(huì)執(zhí)行命令緩沖區(qū)中的命令。
void RunLoopSourcePerformRoutine ( void * info) { RunLoopSource * obj = (RunLoopSource* )info; [obj sourceFired]; }
如果你有調(diào)用CFRunLoopSourceInvalidate方法來(lái)移除輸入源,系統(tǒng)會(huì)調(diào)用輸入源的取消操作。可以在這個(gè)時(shí)候通知客戶(hù)端將要無(wú)效,客戶(hù)端需要移除對(duì)它的引用。下面展示了RunLoopSource對(duì)象注冊(cè)的取消回調(diào)方法。這個(gè)方法發(fā)送RunLoopContext對(duì)象給程序的代理,這次時(shí)通知他們移除引用
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]; }
?
在Run Loop上配置輸入源
下面是RunLoopSource類(lèi)的init和addToCurrentRunLoop方法。init方法創(chuàng)建一個(gè)CGRunLoopSourceRef類(lèi)型是實(shí)際被關(guān)聯(lián)到run loop的。它返回它自己也就是RunLoopSource對(duì)象,這樣外面可以有一個(gè)指向?qū)ο蟮闹羔槨L砑拥骄€(xiàn)程的工作直到工作線(xiàn)程調(diào)用addToCurrentRUnLoop方法后才會(huì)生效,那時(shí)候RunLoopSourceScheduleRoutine回調(diào)方法會(huì)被調(diào)用。只要這個(gè)源加到run loop上之后,線(xiàn)程就可以運(yùn)行run loop來(lái)等待消息了
- ( 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); }
?
客戶(hù)端和輸入源對(duì)應(yīng)
想要輸入源真的有用的話(huà),需要維護(hù)它并且用它向其他線(xiàn)程發(fā)送信號(hào)。輸入源的主要功能是讓相關(guān)的線(xiàn)程掛起,直到他們有事要做的時(shí)候在喚醒。所以就需要讓其他線(xiàn)程直到這個(gè)輸入源并且有一個(gè)和它通訊的方式。
一種讓客戶(hù)端知道輸入源的方法是在第一個(gè)裝載在run loop上時(shí)發(fā)送注冊(cè)請(qǐng)求。可以注冊(cè)任意多的你想要的客戶(hù)端,或者這冊(cè)一個(gè)核心的,然后由它把消息轉(zhuǎn)給其他的。下面展示一個(gè)定義在程序回調(diào)中的注冊(cè)方法(它在RunLoopSource對(duì)象的計(jì)劃方法中被調(diào)用了)。這個(gè)方法接收到一個(gè)RunLoopContext對(duì)象然后加入到列表中。這里也展示了如何注銷(xiāo)它
- ( 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]; }
?
輸入源發(fā)送信號(hào)
在處理好輸入源的數(shù)據(jù)后,客戶(hù)端就可以發(fā)送信號(hào)來(lái)喚醒run loop了。輸入源發(fā)信號(hào)可以讓run loop知道可以準(zhǔn)備好執(zhí)行了。因?yàn)橛锌赡茉谑盏叫盘?hào)時(shí)線(xiàn)程是掛起狀態(tài),所以每次都需要明確的喚醒run loop。否則可能會(huì)導(dǎo)致輸入源延遲執(zhí)行。
下面展示了RunLoopSource的fireCommandsOnRunLoop方法。客戶(hù)端把命令加入到命令緩沖區(qū)并且準(zhǔn)備好執(zhí)行后會(huì)調(diào)用它。
- ( void )fireCommandsOnRunLoop:(CFRunLoopRef)runloop { CFRunLoopSourceSignal(runLoopSource); CFRunLoopWakeUp(runloop); }
注意:不要嘗試去處理SIGHUP或其他線(xiàn)程級(jí)別的信號(hào)。Core Foundation喚醒run loop的信號(hào)不是信號(hào)安全的,不應(yīng)該在你的程序中處理。
?
配置定時(shí)器源
要?jiǎng)?chuàng)建一個(gè)定時(shí)器源,只需要?jiǎng)?chuàng)建一個(gè)定時(shí)器對(duì)象然后加到run loop上。在Cocoa中,可以使用NSTimer類(lèi)來(lái)創(chuàng)建定時(shí)器對(duì)象,在Core Foundation中可以使用CFRunLoopTimerRef。實(shí)際上NSTimer類(lèi)是Core Foundation的擴(kuò)展,提供了一下更方便的方法,比如創(chuàng)建以及添加到線(xiàn)程。
在Cocoa中,可以通過(guò)下面的方法創(chuàng)建定時(shí)器
- scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
- scheduledTimerWithTimeInterval:invocation:repeats:
這些方法會(huì)創(chuàng)建一個(gè)定時(shí)器然后以默認(rèn)類(lèi)型(NSDefaultRunLoopMode)添加到當(dāng)前線(xiàn)程的run loop。也可以手動(dòng)創(chuàng)建一個(gè)NSTImer對(duì)象,然后調(diào)用NSRunLoop的addTimer:forMode:方法添加到run loop上。兩種方法本質(zhì)上做了相同的事,但是可以讓你在不同層面上控制定時(shí)器的配置。比如,如果手動(dòng)的創(chuàng)建定時(shí)器然后添加到run loop,這樣可以使用除了默認(rèn)類(lèi)型以外的類(lèi)型。下面展示了兩種方法。第一個(gè)定時(shí)器延時(shí)1秒后每0.1秒觸發(fā)一次。第二個(gè)定時(shí)器0.2秒后每0.2秒觸發(fā)一次。
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方法來(lái)配置定時(shí)器。雖然下面沒(méi)有傳遞任何用戶(hù)定義的信息,但是你可以使用這個(gè)數(shù)據(jù)結(jié)構(gòu)傳遞你想傳遞的任何數(shù)據(jù)
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);
?
配置Port-Based輸入源
Cocoa和Core Foundation都提供了port-based對(duì)象來(lái)進(jìn)行線(xiàn)程間通訊。
?
配置NSMachPort對(duì)象
要建立和NSMachPort對(duì)象的聯(lián)系,需要?jiǎng)?chuàng)建一個(gè)port對(duì)象然后把它加到線(xiàn)程的run loop中。當(dāng)啟動(dòng)輔助線(xiàn)程時(shí),把同樣的對(duì)象傳遞給線(xiàn)程的入口方法。輔助線(xiàn)程就可以使用相同的對(duì)象來(lái)把消息發(fā)送回來(lái)。
?
主線(xiàn)程代碼實(shí)現(xiàn)
下面展示了啟動(dòng)輔助線(xiàn)程的代碼。Cocoa框架處理了port和run loop的一些中間步驟,所有啟動(dòng)線(xiàn)程的方法比Core Fouundation要短。但是兩種效果是一樣的。不同的是這個(gè)方法直接發(fā)送NSPort對(duì)象給工作線(xiàn)程
- ( 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]; } }
如果要設(shè)置線(xiàn)程的雙向通訊,也需要工作線(xiàn)程把它的port發(fā)送給主線(xiàn)程。
#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. } }
輔助線(xiàn)程代碼實(shí)現(xiàn)
對(duì)于輔助線(xiàn)程,需要配置線(xiàn)程來(lái)使用port和主線(xiàn)程通訊。
下面講述了設(shè)置輔助線(xiàn)程。在創(chuàng)建autorelease pool之后,它創(chuàng)建了一個(gè)工作對(duì)象來(lái)控制線(xiàn)程執(zhí)行。工作對(duì)象的sendCheckinMessage:方法給工作線(xiàn)程創(chuàng)建一個(gè)本地port然后發(fā)送消息給主線(xiàn)程。
+( 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]; }
在使用NSMachPort時(shí),線(xiàn)程間單向通訊可以使用同一個(gè)對(duì)象。也就是說(shuō),當(dāng)前線(xiàn)程創(chuàng)建的port對(duì)象是其他線(xiàn)程接收到的port對(duì)象。
下面是輔助線(xiàn)程的check-in流程。這個(gè)方法設(shè)置了一個(gè)port用來(lái)在以后進(jìn)行通訊,然后把它發(fā)送給主線(xiàn)程。這個(gè)方法使用LaunchThreadWithPort:方法傳過(guò)來(lái)的port對(duì)象
// 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]]; } }
配置NSMessagePort對(duì)象
與NSMessagePort對(duì)象建立連接并不是見(jiàn)到的在線(xiàn)程間傳遞port對(duì)象。遠(yuǎn)程port纖細(xì)必須要有一個(gè)名字。Cocoa用一個(gè)特定的名字注冊(cè)port然后把它傳遞給遠(yuǎn)程線(xiàn)程來(lái)進(jìn)行通訊。下面展示了創(chuàng)建和注冊(cè)消息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];
使用Core Foundation配置port-based輸入源
這里展示如何使用Core Foundation在主線(xiàn)程和工作線(xiàn)程之間設(shè)置一個(gè)雙向通訊通道。
主線(xiàn)程調(diào)用下面的方法來(lái)啟動(dòng)工作線(xiàn)程。里面做的第一件事是設(shè)置了一個(gè)CFMessagePortRef類(lèi)型來(lái)監(jiān)聽(tīng)工作線(xiàn)程的消息。工作線(xiàn)程需要port的名字來(lái)建立連接, 所以名字會(huì)在工作線(xiàn)程的入口傳過(guò)去。名字必須是唯一的。
#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)); }
線(xiàn)程啟動(dòng)之后,主線(xiàn)程在等待反饋時(shí)會(huì)繼續(xù)執(zhí)行其他的任務(wù)。當(dāng)反饋消息回來(lái)時(shí),會(huì)分發(fā)到下面的MainThreadResponseHandler方法。
#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; }
主線(xiàn)程配置好之后,剩下的工作是給工作線(xiàn)程創(chuàng)建port。下面展示了工作線(xiàn)程的入口。
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)入run loop后,其他發(fā)送過(guò)來(lái)的時(shí)間由ProcessClientRequest方法處理。這個(gè)方法怎么實(shí)現(xiàn)取決于這個(gè)線(xiàn)程想做什么。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
