學習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會自動處理之前未處理的訊息,並通知觀察者。具體的順序,如下: 通知觀察者,run loop啟動 通知觀察者任何即將要開始的定時器 通知觀察者任何非基於連接埠的源即將啟動 啟動任何準備好的非基於連接埠的源 如果基於連接埠的源準備好並處於等待狀態,立即啟動;並進入步驟9。 通知觀察者線程進入休眠 將線程之於休眠直到任一下面的事件發生 某一事件到達基於連接埠的源 定時器啟動 設定了run loop的終止時間 run loop喚醒 通知觀察者線程將被喚醒。 處理未處理的事件 如果使用者定義的定時器啟動,處理定時事件並重啟run loop。進入步驟2 如果輸入源啟動,傳遞相應的訊息 run loop喚醒但未終止,重啟。進入步驟2 通知觀察者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對象實現。