關於Autorelease和RunLoop

原文出處:http://blog.csdn.net/cwq9944/article/details/8555104

學習Iphone開發,Autorelease的何時釋放一直是困擾我的一個問題,總覺得大部分文檔提到的延遲釋放,但是這個延遲感念非常模糊,5s叫延遲還是5min叫延遲?所以總覺得擔心我用到標明Autorelease對象的時候由於它堅持不到已經被釋放了。最近查了一下Autorelease到底什麼時候釋放,發現和RunLoop有關,再查RunLoop發現有一大堆的解釋,但是感覺大概意思就是RunLoop就是事件循環,事件包含了:觸屏,NSTimer等,每個線程創建的時候都有一個RunLoop循環,對於每一個Runloop, 系統會隱式創建一個Autorelease pool,這樣所有的release pool會構成一個象CallStack一樣的一個棧式結構,在每一個Runloop結束時,當前棧頂的Autorelease pool會被銷燬,這樣這個pool裏的每個Object會被release。下面是具體解釋:

1,先看AutoRelease:(來源:http://blog.csdn.net/xxq_2011/article/details/7334735)

       1.如果能夠真正的理解autorelease,那麼纔是理解了Objective c的內存管理。Autorelease實際上只是把對release的調用延遲了,對於每一個Autorelease,系統只是把該Object放入了當前的Autorelease pool中,當該pool被釋放時,該pool中的所有Object會被調用Release。
實際上對於 [NSString stringWithFormat:1.0] 這類構造函數返回的對象都是autorelease的。

2. autorelease pool來避免頻繁申請/釋放內存(就是pool的作用了)。這個應該是相對比較好理解的。

   總結:(1)一定要注意Autorelease pool的生存週期,理解Runloop,避免在對象被釋放後使用。

          (2)[NSString stringWithFormat]這類函數返回的對象是不需要再自己release的,它已經被autorelease了, 如果你想把它當一個全局對象使用,那必須自己再retain, 釋放時再release。

  爲什麼需要Auto release ?

 這個auto release有什麼好,象C/C++那樣,自己申請,自己釋放,完全可控不好麼, 這個auto relase 完全不可控,你都不知到它什麼時候會被真正的release。我的理解它有一個作用就是可以做到每個函數對自己申請的對象負責,自己申請,自己釋放,該函數的調用者不需要關心它內部申請對象的管理。 在下面這個例子中,Func1的調用者不需要再去關心obj的釋放。
ClassA *Func1()  

ClassA *obj = [[[ClassA alloc]init]autorelease]; 
return obj; 


(1)在Iphone項目中,大家會看到一個默認的Autorelease pool,程序開始時創建,程序退出時銷燬,按照對Autorelease的理解,豈不是所有autorelease pool裏的對象在程序退出時才release, 這樣跟內存泄露有什麼區別?

答案是,對於每一個Runloop, 系統會隱式創建一個Autorelease pool,這樣所有的release pool會構成一個象CallStack一樣的一個棧式結構,在每一個Runloop結束時,當前棧頂的Autorelease pool會被銷燬,這樣這個pool裏的每個Object會被release。

那什麼是一個Runloop呢? 一個UI事件,Timer call, delegate call, 都會是一個新的Runloop。例子如下:


NSString* globalObject; 
- (void)applicationDidFinishLaunching:(UIApplication *)application 

globalObject = [[NSString alloc] initWithFormat:@"Test"]; 
NSLog(@"Retain count after create: %d", [globalObject retainCount]); // output 1.
[globalObject retain]; 
NSLog(@"Retain count after retain: %d", [globalObject retainCount]); // output 2.

- (void)applicationWillTerminate:(UIApplication *)application 

NSLog(@"Retain count after Button click runloop finished: %d", [globalObject retainCount]);
// 輸出1. Button click loop finished, it's autorelease pool released, globalObject get released once.

-(IBAction)onButtonClicked 

[globalObject autorelease]; 
NSLog(@"Retain count after autorelease: %d", [globalObject retainCount]); 
// 輸出2。 Autorelease被call, globalObject被加如當前的AutoreleaePool。 

2,想了解RunLoop的可以看下這篇講解(來源:http://www.cnblogs.com/scorpiozj/archive/2011/05/26/2058167.html)

Run Loop

學習過程中,將Threading PG中的Run Loops翻譯了下,權當是做爲筆記。原文見 Run Loops。

20110526

轉載請註明,謝謝。

http://www.cnblogs.com/scorpiozj/

Run loops是線程的基礎架構部分。一個run loop就是一個事件處理循環,用來不停的調配工作以及處理輸入事件。使用run loop的目的是使你的線程在有工作的時候工作,沒有的時候休眠。

Run loop的管理並不完全是自動的。你仍必須設計你的線程代碼以在適當的時候啓動run loop並正確響應輸入事件。Cocoa和CoreFundation都提供了run loop對象方便配置和管理線程的run loop。你創建的程序不需要顯示的創建run loop;每個線程,包括程序的主線程(main thread)都有與之相應的run loop對象。但是,自己創建的次線程是需要手動運行run loop的。在carbon和cocoa程序中,程序啓動時,主線程會自行創建並運行run loop。

接下來的部分將會詳細介紹run loop以及如何爲你的程序管理run loop。關於run loop對象可以參閱sdk文檔。

解析Run Loop

run loop,顧名思義,就是一個循環,你的線程在這裏開始,並運行事件處理程序來響應輸入事件。你的代碼要有實現循環部分的控制語句,換言之就是要有while或for語句。在run loop中,使用run loop對象來運行事件處理代碼:響應接收到的事件,啓動已經安裝的處理程序。

Run loop處理的輸入事件有兩種不同的來源:輸入源(input source)和定時源(timer source)。輸入源傳遞異步消息,通常來自於其他線程或者程序。定時源則傳遞同步消息,在特定時間或者一定的時間間隔發生。兩種源的處理都使用程序的某一特定處理路徑。

圖1-1顯示了run loop的結構以及各種輸入源。輸入源傳遞異步消息給相應的處理程序,並調用runUntilDate:方法退出。定時源則直接傳遞消息給處理程序,但並不會退出run loop。

                        圖1-1 run loop結構和幾種源

除了處理輸入源,run loop也會生成關於run loop行爲的notification。註冊的run-loop 觀察者可以收到這些notification,並做相應的處理。可以使用Core Foundation在你的線程註冊run-loop觀察者。

下面介紹run loop的組成,以及其運行的模式。同時也提及在處理程序中不同時間發送不同的notification。

Run Loop Modes

Run loop模式是所有要監視的輸入源和定時源以及要通知的註冊觀察者的集合。每次運行run loop都會指定其運行在哪個模式下。以後,只有相應的源會被監視並允許接收他們傳遞的消息。(類似的,只有相應的觀察者會收到通知)。其他模式關聯的源只有在run loop運行在其模式下才會運行,否則處於暫停狀態。

通常代碼中通過指定名字來確定模式。Cocoa和core foundation定義了默認的以及一系列常用的模式,都是用字符串來標識。當然你也可以指定字符串來自定義模式。雖然你可以給模式指定任何名字,但是所有的模式內容都是相同的。你必須添加輸入源,定時器或者run loop觀察者到你定義的模式中。

通過指定模式可以使得run loop在某一階段只關注感興趣的源。大多數時候,run loop都是運行在系統定義的默認模式。但是模態面板(modal panel)可以運行在 “模態”模式下。在這種模式下,只有和模態面板相關的源可以傳遞消息給線程。對於次線程,可以使用自定義模式處理時間優先的操作,即屏蔽優先級低的源傳遞消息。

Note:模式區分基於事件的源而非事件的種類。例如,你不可以使用模式只選擇處理鼠標按下或者鍵盤事件。你可以使用模式監聽端口, 暫停定時器或者其他對源或者run loop觀察者的處理,只要他們在當前模式下處於監聽狀態。

表1-1列出了cocoa和Core Foundation預先定義的模式。

              表1-1

輸入源

輸入源向線程發送異步消息。消息來源取決於輸入源的種類:基於端口的輸入源和自定義輸入源。基於端口的源監聽程序相應的端口,而自定義輸入源則關注自定義的消息。至於run loop,它不關心輸入源的種類。系統會去實現兩種源供你使用。兩類輸入源的區別在於如何顯示的:基於端口的源由內核自動發送,而自定義的則需要人工從其他線程發送。

當你創建輸入源,你需要將其分配給run loop中的一個或多個模式。模式只會在特定事件影響監聽的源。大多數情況下,run loop運行在默認模式下,但是你也可以使其運行在自定義模式。若某一源在當前模式下不被監聽,那麼任何其生成的消息只有當run loop運行在其關聯的模式下才會被傳遞。

下面討論這幾種輸入源。

http://www.cnblogs.com/scorpiozj/

基於端口的源:

cocoa和core foundation爲使用端口相關的對象和函數創建的基於端口的源提供了內在支持。Cocoa中你從不需要直接創建輸入源。你只需要簡單的創建端口對象,並使用NSPort的方法將端口對象加入到run loop。端口對象會處理創建以及配置輸入源。

在core foundation,你必須手動的創建端口和源,你都可以使用端口類型(CFMachPortRef,CFMessagePortRef,CFSocketRef)來創建。

更多例子可以看 配置基於端口的源。

自定義輸入源:

在Core Foundation程序中,必須使用CFRunLoopSourceRef類型相關的函數來創建自定義輸入源,接着使用回調函數來配置輸入源。Core Fundation會在恰當的時候調用回調函數,處理輸入事件以及清理源。

除了定義如何處理消息,你也必須定義源的消息傳遞機制——它運行在單獨的進程,並負責傳遞數據給源和通知源處理數據。消息傳遞機制的定義取決於你,但最好不要過於複雜。

關於創建自定義輸入源的例子,見 定義自定義輸入源。關於自定義輸入源的信息參見CFRunLoopSource。

Cocoa Perform Selector Sources:

除了基於端口的源,Cocoa提供了可以在任一線程執行函數(perform selector)的輸入源。和基於端口的源一樣,perform selector請求會在目標線程上序列化,減緩許多在單個線程上容易引起的同步問題。而和基於端口的源不同的是,perform selector執行完後會自動清除出run loop。

當perform selector在其它線程中執行時,目標線程須有一活動中的run loop。對於你創建的線程而言,這意味着線程直到你顯示的開始run loop否則處於等待狀態。然而,由於主線程自己啓動run loop,在程序調用applicationDidFinishlaunching:的時候你會遇到線程調用的問題。因爲Run loop通過每次循環來處理所有排列的perform selector調用,而不時通過每次的循環迭代來處理perform selector。

表1-2列出了NSObject可以在其它線程使用的perform selector。由於這些方法時定義在NSObject的,你可以在包括POSIX的所有線程中使用只要你有objc對象的訪問權。注意這些方法實際上並沒有創建新的線程以運行perform selector。

                      表1-2

定時源

定時源在預設的時間點同步地傳遞消息。定時器時線程通知自己做某事的一種方法。例如,搜索控件可以使用定時器,當用戶連續輸入的時間超過一定時間時,就開始一次搜索。這樣,用戶就可以有足夠的時間來輸入想要搜索的關鍵字。

儘管定時器和時間有關,但它並不是實時的。和輸入源一樣,定時器也是和run loop的運行模式相關聯的。如果定時器所在的模式未被run loop監視,那麼定時器將不會開始直到run loop運行在相應的模式下。類似的,如果定時器在run loop處理某一事件時開始,定時器會一直等待直到下次run loop開始相應的處理程序。如果run loop不再運行,那定時器也將永遠不開始。

你可以選擇定時器工作一次還是定時工作。如果定時工作,定時器會基於安排好的時間而非實際時間,自動的開始。舉個例子,定時器在某一特定時間開始並設置5秒重複,那麼定時器會在那個特定時間後5秒啓動,即使在那個特定時間定時器延時啓動了。如果定時器延遲到接下來設定的一個會多個5秒,定時器在這些時間段中也只會啓動一次,在此之後,正常運行。(假設定時器在時間1,5,9。。。運行,如果最初延遲到7才啓動,那還是從9,13,。。。開始)。


Run Loop觀察者

源是同步或異步的傳遞消息,而run loop觀察者則是在運行run loop的時候在特定的時候開始。你可以使用run loop觀察者來爲某一特定事件或是進入休眠的線程做準備。你可以將觀察者將以下事件關聯:

  • Run loop入口
  • Run loop將要開始定時
  • Run loop將要處理輸入源
  • Run loop將要休眠
  • Run loop被喚醒但又在執行喚醒事件前
  • Run loop終止

你可以給cocoa和carbon程序隨意添加觀察者,但是如果你要定義觀察者的話就只能使用core fundation。使用CFRunLoopObserverRed類型來創建觀察者實例,它會追蹤你自定義的回調函數以及其它你感興趣的地方。

和定時器類似,觀察者可以只用一次或循環使用。若只用一次,那在結束的時候會移除run loop,而循環的觀察者則不會。你需要制定觀察者是一次/多次使用。

消息的run loop順序

每次啓動,run loop會自動處理之前未處理的消息,並通知觀察者。具體的順序,如下:

  1. 通知觀察者,run loop啓動
  2. 通知觀察者任何即將要開始的定時器
  3. 通知觀察者任何非基於端口的源即將啓動
  4. 啓動任何準備好的非基於端口的源
  5. 如果基於端口的源準備好並處於等待狀態,立即啓動;並進入步驟9。
  6. 通知觀察者線程進入休眠
  7. 將線程之於休眠直到任一下面的事件發生
  • 某一事件到達基於端口的源
  • 定時器啓動
  • 設置了run loop的終止時間
  • run loop喚醒
  1. 通知觀察者線程將被喚醒。
  2. 處理未處理的事件
  • 如果用戶定義的定時器啓動,處理定時事件並重啓run loop。進入步驟2
  • 如果輸入源啓動,傳遞相應的消息
  • run loop喚醒但未終止,重啓。進入步驟2
  1. 通知觀察者run loop結束。

(標號應該連續,不知道怎麼改)

因爲觀察者的消息傳遞是在相應的事件發生之前,所以兩者之間可能存在誤差。如果需要精確時間控制,你可以使用休眠和喚醒通知以此來校對實際發生的事件。

因爲定時器和其它週期性事件那是在run loop運行後才啓動,撤銷run loop也會終止消息傳遞。典型的例子就是鼠標路徑追蹤。因爲你的代碼直接獲取到消息而不是經由程序傳遞,從而不會在實際的時間開始而須使得鼠標追蹤結束並將控制權交給程序後才行。

使用run loop對象可以喚醒Run loop。其它消息也可以喚醒run loop。例如,添加新的非基於端口的源到run loop從而可以立即執行輸入源而不是等待其他事件發生後再執行。

何時使用Run Loop

http://www.cnblogs.com/scorpiozj/archive/2011/05/26/2058167.html

只有在爲你的程序創建次線程的時候,才需要運行run loop。對於程序的主線程而言,run loop是關鍵部分。Cocoa和carbon程序提供了運行主線程run loop的代碼同時也會自動運行run loop。IOS程序UIApplication中的run方法在程序正常啓動的時候就會啓動run loop。同樣的這部分工作在carbon程序中由RunApplicationEventLoop負責。如果你使用xcode提供的模板創建的程序,那你永遠不需要自己去啓動run loop。

而對於次線程,你需要判斷是否需要run loop。如果需要run loop,那麼你要負責配置run loop並啓動。你不需要在任何情況下都去啓動run loop。比如,你使用線程去處理一個預先定義好的耗時極長的任務時,你就可以毋需啓動run loop。Run loop只在你要和線程有交互時才需要,比如以下情況:

  • 使用端口或自定義輸入源和其他線程通信
  • 使用定時器
  • cocoa中使用任何performSelector
  • 使線程履行週期性任務

如果決定在程序中使用run loop,那麼配置和啓動都需要自己完成。和所有線程編程一樣,你需要計劃好何時退出線程。在退出前結束線程往往是比被強制關閉好的選擇。詳細的配置和推出run loop的信息見 使用run loop對象。

使用Run loop對象

run loop對象提供了添加輸入源,定時器和觀察者以及啓動run loop的接口。每個線程都有唯一的與之關聯的run loop對象。在cocoa中,是NSRunLoop對象;而在carbon或BSD程序中則是指向CFRunLoopRef類型的指針。

獲得run loop對象

獲得當前線程的run loop,可以採用:

  • cocoa:使用NSRunLoop的currentRunLoop類方法
  • 使用CFRunLoopGetCurrent函數

雖然CFRunLoopRef類型和NSRunLoop對象並不完全等價,你還是可以從NSRunLoop對象中獲取CFRunLoopRef類型。你可以使用NSRunLoop的getCFRunLoop方法,返回CFRunLoopRef類型到Core Fundation中。因爲兩者都指向同一個run loop,你可以任一替換使用。

配置run loop

在次線程啓動run loop前,你必須至少添加一類源。因爲如果run loop沒有任何源需要監視的話,它會在你啓動之際立馬退出。

此外,你也可以添加run loop觀察者來監視run loop的不同執行階段。首先你可以創建CFRunLoopObserverRef類型並使用CFRunLoopAddObserver將它添加金run loop。注意即使是cocoa程序,run loop觀察者也需要由core foundation函數創建。

以下代碼3-1實現了添加觀察者進run loop,代碼簡單的建立了一個觀察者來監視run loop的所有活動,並將run loop的活動打印出來。

 Creating a run loop observer

如果線程運行事件長,最好添加一個輸入源到run loop以接收消息。雖然你可以使用定時器,但是定時器一旦啓動後當它失效時也會使得run loop退出。雖然定時器可以循環使得run loop運行相對較長的時間,但是也會導致週期性的喚醒線程。與之相反,輸入源會等待某事件發生,於是線程只有當事件發生後纔會從休眠狀態喚醒。

啓動run loop

run loop只對程序的次線程有意義,並且必須添加了一類源。如果沒有,在啓動後就會退出。有幾種啓動的方法,如:

  • 無條件的
  • 預設的時間
  • 特定的模式

無條件的進入run loop是最簡單的選擇,但也最不提倡。因爲這樣會使你的線程處在一個永久的run loop中,這樣的話你對run loop本身的控制就會很小。你可以添加或移除源,定時器,但是隻能通過殺死進程的辦法來退出run loop。並且這樣的run loop也沒有辦法運行在自定義模式下。

用預設時間來運行run loop是一個比較好的選擇,這樣run loop在某一事件發生或預設的事件過期時啓動。如果是事件發生,消息會被傳遞給相應的處理程序然後run loop退出。你可以重新啓動run loop以處理下一個事件。如果是時間過期,你只需重啓run loop或使用定時器做任何的其他工作。**

此外,使run loop運行在特定模式也是一個比較好的選擇。模式和預設時間不是互斥的,他們可以同時存在。模式對源的限制在run loop模式部分有詳細說明。

Listing3-2代碼描述了線程的整個結構。代碼的關鍵是說明了run loop的基本結構。必要時,你可以添加自己的輸入源或定時器,然後重複的啓動run loop。每次run loop返回,你要檢查是否有使線程退出的條件發生。代碼中使用了Core Foundation的run loop程序,這樣就能檢查返回結果從而判斷是否要退出。若是cocoa程序,也不需要關心返回值,你也可以使用NSRunLoop的方法運行run loop(代碼見listing3-14)

Listing 3-2 Running a run loop

因爲run loop有可能迭代啓動,也就是說你可以使用CFRunLoopRun,CFRunLoopRunInMode或者任一NSRunLoop的方法來啓動run loop。這樣做的時候,你可以使用任何模式啓動迭代的run loop,包括被外層run loop使用的模式。

退出run loop

在run loop處理事件前,有兩種方法使其退出:

  • 設置超時限定
  • 通知run loop停止

如果可以配置的話,使用第一種方法是較好的選擇。這樣,可以使run loop完成所有正常操作,包括髮送消息給run loop觀察者,最後再退出。

使用CFRunLoopStop來停止run loop也有類似的效果。Run loop也會把所有未發送的消息發送完後再退出。與設置時間的區別在於你可以在任何情況下停止run loop。

儘管移除run loop的輸入源和定時器也可以使run loop退出,但這並不是可靠的退出run loop的辦法。一些系統程序會添加輸入源來處理必須的事件。而你的代碼未必會考慮到這些,這樣就沒有辦法從系統程序中移除,從而就無法退出run loop。

線程安全和run loop對象

線程是否安全取決於你使用哪種API操縱run loop。Core Foundation中的函數通常是線程安全的可以被任意線程調用。但是,如果你改變了run loop的配置然後需要進行某些操作,你最好還是在run loop所在線程去處理。如果可能的話,這樣是個好習慣。

至於Cocoa的NSRunLoop則不像Core Foundation具有與生俱來的線程安全性。你應該只在run loop所在線程改變run loop。如果添加yuan或定時器到屬於另一個線程的run loop,程序會崩潰或發生意想不到的錯誤。

Run loop 源的配置

下面的例子說明了如果使用cocoa和core foundation來建立不同類型的輸入源。

定義自定義輸入源

遵循下列步驟來創建自定義的輸入源:

  • 輸入源要處理的信息
  • 使感興趣的客戶知道如何和輸入源交互的調度程序
  • 處理客戶發送請求的程序
  • 使輸入源失效的取消程序

由於你自己創建源來處理消息,實際配置設計得足夠靈活。調度,處理和取消程序是你創建你得自定義輸入源時總會需要用到得關鍵程序。但是,輸入源其他的大部分行爲都是由其他程序來處理。例如,由你決定數據傳輸到輸入源的機制,還有輸入源和其他線程的通信機制。

圖3-2列舉了自定義輸入源的配置。在這個例子中,程序的主線程保持了輸入源,輸入源所需的命令緩衝區和輸入源所在的run loop的引用。當主線程有任務,需要分發給目標線程,主線程會給命令緩衝區發送命令和必須的信息,這樣活動線程就可以開始執行任務。(因爲主線程和輸入源所在線程都須訪問命令緩衝區,所以他們的操作要注意同步。)一旦命令傳送了,主線程會通知輸入源並且喚醒活動線程的run loop。而一收到喚醒命令,run loop會調用輸入源的處理部分,由它來執行命令緩衝區中相應的命令。

                圖3-2

下面解釋下上圖的關鍵代碼。

定義輸入源

定義輸入源需要使用Core Foundation來配置run loop源並把它添加到run loop。基本的函數是C函數,當然你也可以用objc或C++來封裝操作。

圖3-2中的輸入源使用了objc對象來管理命令緩衝區和run loop。Listing3-3說明了此對象的定義:RunLoopSource對象管理着命令緩衝區並以此來接收其他線程的消息;RunLoopContext對象是一個用於傳遞RunLoopSource對象和run loop引用給程序主線程的一個容器。

Listing 3-3 The custom input source object definition

雖然輸入源的數據定義是objc代碼,但是將源添加進run loop卻需要c的回調函數。上述函數在像Listing3-4一樣,在添加時調用。因爲這個輸入源只有一個客戶(即主線程),它使用調度函數發送註冊信息給程序的代理(delegate)。當代理需要和輸入源通信時,就可以使用RunLoopContext對象實現。

Listing 3-4 Scheduling a run loop source

一個重要的回調函數就是用來處理自定義數據。Lising3-5說明了如何調用這個回調函數。這裏只是簡單的將請求傳遞到sourceFired方法,然後繼續處理在命令緩存區的命令。

Listing 3-5 Performing work in the input source

使用CFRunLoopSourceInvalidate函數移除輸入源,系統會調用輸入源的取消程序。你可以以此通知客戶輸入源不再有效,客戶可以釋放輸入源的引用。Listing3-6說明了取消回調函數的調用,其中給另一個RunLoopContext對象發送了程序代理,通知代理去除源的引用。

複製代碼
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

Listing3-7說明了RunLoopSource的init和addToCurrentRunLoop函數。Init函數創建CFRunLoopSourceRef類型,傳遞RunLoopSource對象做爲信息這樣回調函數持有對象的引用。輸入源的安裝當工作線程運行addToCurrentRunLoop方法,然後調用RunLoopSourceScheduledRoutine回調函數。一旦源被添加,線程就運行run loop監聽事件。

Listing 3-7 Installing the run loop source

統籌輸入源的客戶

爲了使添加的輸入源有用,你需要處理源以及從其他線程發送信號。輸入源的主要工作就是將與源關聯的線程休眠,直到有事件發生。這就意味着程序中的線程必須知道輸入源信息並有辦法與之 通信。

通知客戶關於輸入源信息的方法之一就是當輸入源安裝完成後發送註冊請求。你可以註冊輸入源到你需要的客戶,或者通過註冊中間代理由代理將輸入源到感興趣的客戶。Listing3-8說明了程序代理定義的註冊方法以及它在RUnLoopSource對象的調度函數調用時如何運行。函數接收RUnLoopSource提供的RunLoopContext對象,然後將其膠乳源隊列。另外,也說明了源移除run loop時候的取消註冊方法。

Listing 3-8 Registering and removing an input source with the application delegate

通知輸入源

客戶發送數據到輸入源後,必須發信號通知源並且喚醒run loop。發信好意味着讓run loop明白源已經做好處理消息的準備。因爲信號發生的時候線程可能休眠着,你必須自己喚醒run loop。如果不這樣做的話會導致延遲處理消息。

Listing3-9說明了RunLoopSource對象的fireCommandsOnRunLoop方法。客戶如果準備好處理加入緩衝區的命令後會運行此方法。

Listing 3-9 Waking up the run loop

配置定時源

創建定時源你所需要做的就是創建定時器並加入run loop調度。Cocoa程序中使用NSTimer類而Core Foundation中使用CFRunLoopTimerRef類型。本質上,NSTimer類是Core Foundation的簡單擴展,這樣就提供了便利的特徵,例如都能使用相同的函數創建和調配定時器。

Cocoa中可以使用以下函數創建並調配:

上述方法創建了定時器並使之以默認模式添加到當前線程的run loop。你可以自己調度定時器如果你選擇自己創建定時器並使用addTimer:forMode:方法添加到run loop。兩種方法都做了相同的事,區別在於你對定時器的控制權。如果你自己創建的話,你可以選擇添加的模式。Listing3-10說明了如果這樣做。第一個定時器在初始化後1秒開始可以後每隔0.1秒運行,第二個定時器則在初始化後0。2秒開始以後每隔0。2運行。

Listing 3-10 Creating and scheduling timers using NSTimer

Listing3-11是使用Core Foundation函數配置定時器的代碼。這個例子中文本結構例沒有任何用戶定義的信息,但是你可以使用這個結構體傳遞任何你想傳遞的信息給定時器。關於結構體的詳細信息,參見CFRunLoopTimer。

Listing 3-11 Creating and scheduling a timer using Core Foundation

配置基於端口的輸入源

cocoa和core foundation都提供了基於端口的對象用於線程或進程間的通信。下面的部分說明了使用幾種不同的端口對象建立端口通信。

配置NSMachPort對象

建立和NSMachPort對象的本地連接,你需要創建端口對象並將之加入主線程的run loop。當運行次線程的時候,你傳遞端口對象到線程的入口點。次線程可以使用相同的端口對象將消息返回給主線程。

主線程的實現代碼

Listing3-12說明了在主線程啓動次線程的方法。因爲cocoa框架提供了許多配置端口和run loop的中間步驟所以lauchThread方法比相應的core foundation版本(Listing3-17)要明顯簡短。但是兩種方法的本質幾乎是一樣的,唯一的區別就是在cocoa中直接發送了NSPort對象而不是發送本地端口名。

Listing 3-12 Main thread launch method

爲了建立線程間的雙向通信,你需要在簽到消息中從活動線程發送自己的本地端口到主線程。接收到簽到消息後你的主線程就可以知道次線程運行正常並且提供了發送消息給次線程的方法。

Listing3-13說明了活動線程的handlePortMessage:方法,當由數據到達線程的本地端口,次方法調用。當簽到消息收到後,此方法可以直接獲取到次線程的端口並保存下來以做後續之用。

Listing 3-13 Handling Mach port messages

次線程代碼實現

對於次線程,你必須配置線程並使用特定的端口以發送消息返回至活動線程。

Listing3-14說明了如何建立活動線程。創建了自動釋放池後,緊接着建立了活動對象驅動線程運行。活動對象的sendCheckinMessage:方法創建了本地端口併發送簽到消息回主線程。

Listing 3-14 Launching the worker thread using Mach ports

使用NSMachPort,本地和遠程線程都使用相同的端口對象救星線程的單邊通信。就是說,一個線程的本地端口對象是另一個線程的遠程端口對象。

Listing3-15說明了次線程的簽到程序,它建立了本地端口用於通信,然後發送簽到消息。它使用LaunchThreadWithPort:方法中收到的端口對象做爲目標消息。

Listing 3-15 Sending the check-in message using Mach ports

配置NSMessagePort對象

建立和NSMeaasgePort的本地連接,你將不能隨意在線程間傳遞端口對象。遠程消息端口必須通過名字來獲得。在cocoa中這需要你給本地端口註冊名字然後將名字傳遞到遠程線程這樣遠程線程可以獲得合適的端口對象用於通信。Listing3-16說明了這些步驟。

Listing 3-16 Registering a message port

在core foundation配置基於端口的源

這部分說明了在corefoundation建立程序主線程和活動線程的雙邊通道。

Listing3-17是程序主線程啓動活動線程:第一是建立CFMessagePortRef類型監聽活動線程的消息。活動線程需要端口的名字來建立連接,這樣線程名就回從活動線程的入口傳入。端口名在當前的用戶菜單下須始終唯一否則會出錯。

Listing 3-17 Attaching a Core Foundation message port to a new thread

端口建立線程啓動後,主線程繼續執行,邊等到線程簽到。到收到簽到消息後,主線程使用MainThreadResponsehandler來分發消息,如Listing3-18。這個函數提取活動線程的端口名,並創建用於未來通信的管道。

Listing 3-18 Receiving the checkin message

主線程配置好後,剩下的唯一事情是讓新創建的工作線程創建自己的端口然後簽到。Listing3-19顯示了工作線程的入口函數。函數獲取了端口名並使用它來創建和主線程的遠程連接。然後這個函數創建自己的本地斷後,安裝到線程的run loop,最後連同本地端口名一起發回主線程簽到。

Listing 3-19 Setting up the thread structures

一旦線程啓動run loop,所有發送到線程端口的事件都會由ProcessClientRequest函數處理。函數的具體實現依賴於線程的工作方式,這裏就不舉例了。

翻譯完結,有很多術語不知道怎麼對應,謝謝各位能看到此處。

最後,總結一下:

普通做ios應用的都不會直接接觸到run loop,但是如果是做線程間通信或程序通信之類的就需要好好理解和掌握run loop。


發佈了11 篇原創文章 · 獲贊 2 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章