NSRunloop顧名思義,就是一個消息循環,它會偵測輸入源(input source)和定時源(timer source),而後作回調處理。這和windows的消息處理很是相似,只不過你沒法看到相似SendMessage,PostMessage,GetMessage的方法,NSRunloop已經封裝了這些細節。那NSRunloop的好處是否是隻有封裝細節,而後方便調用呢?結果是否認的。看apple官方文檔(多線程編程指南)描述: "run loop 是用來在線程上管理事件異步到達的基礎設施......run loop在沒有任何事件處理的時候會把它的線程置於休眠狀態,它消除了消耗CPU週期輪詢,並防止處理器自己進入休眠狀態並節省電源。" 看見沒,消除CPU空轉纔是它最大的用處。php
因此NSRunloop的重點就是:html
1.run loop 用來監聽長耗時的異步事件,若是用不到異步事件,就不用扯這個東西了(給你的面試官這麼說吧)。例如,網絡回調,不管是apple提供的NSURL仍是開源的ASIHttpRequest,都是用NSRunloop來監聽網絡事件(TCP/IP的堆棧)。(這我的的經歷能夠參考 http://www.blogjava.net/writegull/archive/2012/07/25/383926.html)java
2.run loop解決了CPU空轉。面試
工做原理圖以下編程
圖 3-1顯示了run loop的概念結構以及各類源。輸入源傳遞異步消息給相應的處理例程,並調用runUntilDate:方法來退出(在線程裏面相關的NSRunLoop對象調用)。定時源則直接傳遞消息給處理例程,但並不會退出run loop。 windows
從上圖中咱們能夠看出,每一個線程都有一個默認的NSRunloop。主線程的NSRunloop默認是運行的。非主線程的NSRunloop默認是沒有運行的,須要爲NSRunloop添加一個事件,而後去run,通常狀況下沒有必要啓用線程的runloop,除非須要長久地監測某個異步事件。api
拿具體的應用舉個例子,NSURLConnection網絡數據請求,默認是異步的方式,其實現原理就是建立以後將其做爲事件源加入到當前的 RunLoop,而等待網絡響應以及網絡數據接受的過程則在一個新建立的獨立的線程中完成,當這個線程處理到某個階段的時候好比獲得對方的響應或者接受完了網絡數據以後便通知以前的線程去執行其相關的delegate方法。因此在Cocoa中常常看到scheduleInRunLoop:forMode: 這樣的方法,這個即是將其加入到事件源中,當檢測到某個事件發生的時候,相關的delegate方法便被調用。網絡
先提出一個問題,在Iphone項目中,你們會看到一個默認的Autorelease pool,程序開始時建立,程序退出時銷燬,按照對Autorelease的理解,豈不是全部autorelease pool裏的對象在程序退出時才release, 這樣跟內存泄露有什麼區別?結果是,對於每個Runloop, 系統會隱式建立一個Autorelease pool,這樣全部的release pool會構成一個象CallStack同樣的一個棧式結構,在每個Runloop結束時,當前棧頂的Autorelease pool會被銷燬,這樣這個pool裏的每一個Object會被release。多線程
那什麼是一個runloop?一個UI事件,一個timer,一個系統delegate都稱之爲runloop(不是NSRunloop),runloop其實是從接收消息,而後處理完消息的一個完整過程。app
爲了更加形象說明auto release pool機制,下面舉例:
NSString* str1是assign。
UI事件:UIButton的target-action機制,在action中建立一個autorelease的UILabel對象,並賦值,在action中打印出值,action執行完畢,這個時候runloop結束,autorelease pool被釋放,label也被釋放,因此再調用這個對象的值時,出現bad_exec_access。
多說一句,只有以上提到了3種runloop纔會自動建立autorelease pool,thread是不會自動建立的,因此咱們能夠看到子線程中會有手動寫的autorelease pool代碼。這點之前搞混過,切記!
就我的經驗而言,在iphone線程中使用異步NSURLConnection的經驗能夠說是一個徹底和愉悅搭不上邊的事情。他給我帶來的麻煩可真很多。例如,前幾天,幫客戶定位一個問題的時候發生的事情。
事情通過是這樣的:客戶反饋,沒法正常使用咱們提供的某個和網絡相關的功能,網絡回調沒有收到。可是其餘回調能夠正常工做,而且全部回調都是以一樣的邏輯放在某個地方的。
我先確認了他的使用方式是否正確,並確認了輸入參數的正確性,而且驗證了回調的正確設置以及回調函數的使用無誤。一切都沒有問題,可是就是沒法收到回調。
一切都那麼的神奇,功能的調用並無什麼特殊的,可是就單單這個功能工做不正常。中間又通過一些確認,發現數據並無到達服務端。可是一樣的使用方式在咱們本身的環境下工做又是正常的。
花了將近5個小時,仍是沒有發現緣由,就在我準備放棄的時候,忽然想到,莫非他的調用位置是線程中調用?通過確認,發現果真是在線程中調用的!讓他們使用performOnMainThread的方式調用,終於解決了問題。
之前遇到過一次在線程中調用異步網絡的狀況,請教同事,同事告知,異步網絡須要本身的RunLoop,因此要在線程中使用異步網絡必須有本身的RunLoop,能夠將他放到主線程的RunLoop。
可是,這樣子,多線程的性能必然被下降,由於這樣網絡的工做就都是在主線程中完成的。同時懷疑,這樣子的網絡性能設計不是很低下!
恰好有點時間,就仔細的翻了一下蘋果的開發文檔,發現其中指出,全部的NSThread都有一個屬於本身的NSRunLoop,而NSURLConnection的回調都會回調到當前線程中!
這個和我目前遇到的徹底不同!我沒法在當前線程中收到回調!
又仔細在網絡上搜索以後發現,原來,是由於網絡回調的時候線程的NSRunLoop已經被無效的緣由。
咱們常見的線程中網絡調用是這樣的:
- (void)threadFunc
{
NSAutoReleasePool *pool = [NSAutoReleasePool new];
//do something
……
//send your request
[NSURLConnection connectionWithRequest:xxx];
//do some other thing
……
[pool release];
}
通常來講,這個函數會很快走完,線程就結束了。因此,當網絡響應回來的時候,你的線程已經結束了。你的網絡回調依賴於該線程的NSRunLoop,可是線程已經結束了,因此你的網絡回調就沒法收到!!同理,全部的performAfterDelay之類的api以及NSTimer的在線程中工做都不正常!
那麼,該如何讓他正常工做呢?很簡單,作完事情以前,讓線程不要結束,保持空轉狀態就好了。
- (void)threadFunc
{
NSAutoReleasePool *pool = [NSAutoReleasePool new];
//do something
……
//send your request
[NSURLConnection connectionWithRequest:xxx];
//do some other thing
……
while(shouldExit) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } [pool release]; }