IOS-Run loop學習總結

來源:互聯網
上載者:User

標籤:蘋果   ted   def   mes   add   object類   ble   基礎   調用   

不知道大家有沒有想過這個問題,一個應用開始執行以後放在那裡,假設不正確它進行不論什麼操作。這個應用就像精巧了一樣,不會自發的有不論什麼動作發生。可是假設我們點擊介面上的一個button。這個時候就會有對應的button響應事件發生。給我們的感覺就像應用一直處於隨時待命的狀態。在沒人操作的時候它一直在歇息,在讓它幹活的時候,它就能立刻響應。事實上,這就是run loop的功勞。

一、線程與run loop

1.1 線程任務的類型
再來說說線程。有些線程執行的任務是一條直線,起點到終點;而還有一些線程要乾的活則是一個圓。不斷迴圈,直到通過某種方式將它終止。

直線線程如簡單的Hello World,執行列印完,它的生命週期便結束了。像曇花一現那樣;圓類型的如作業系統,一直執行直到你關機。在IOS中,圓型的線程就是通過run loop不停的迴圈實現的。

1.2 線程與run loop的關係
Run loop,正如其名,loop表示某種迴圈,和run放在一起就表示一直在執行著的迴圈。實際上,run loop和線程是緊密相連的,能夠這樣說run loop是為了線程而生,沒有線程。它就沒有存在的必要。Run loops是線程的基礎架構部分,Cocoa和CoreFundation都提供了run loop對象方便配置和管理線程的run loop(以下都已Cocoa為例)。

每個線程。包含程式的主線程(main thread)都有與之對應的run loop對象。

1.2.1 主線程的run loop預設是啟動的。


iOS的應用程式裡面,程式啟動後會有一個例如以下的main() 函數:

  int main(int argc, char *argv[])     {            @autoreleasepool {              return UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));           }  }

重點是UIApplicationMain() 函數,這種方法會為main thread 設定一個NSRunLoop 對象,這就解釋了本文開始說的為什麼我們的應用能夠在無人操作的時候歇息,須要讓它幹活的時候又能立刻響應。

1.3 關於run loop的幾點說明
1.3.1 Cocoa中的NSRunLoop類並非安全執行緒的

我們不能再一個線程中去操作另外一個線程的run loop對象。那非常可能會造成意想不到的後果。

只是幸運的是CoreFundation中的不透明類CFRunLoopRef是安全執行緒的,並且兩種類型的run loop全然能夠混合使用。Cocoa中的NSRunLoop類能夠通過執行個體方法:

  • (CFRunLoopRef)getCFRunLoop;

擷取對應的CFRunLoopRef類。來達到安全執行緒的目的。

1.3.2 Run loop的管理並不全然是自己主動的。
我們仍必須設計線程代碼以在適當的時候啟動run loop並正確響應輸入事件,當然前提是線程中須要用到run loop。並且,我們還須要使用while/for語句來驅動run loop能夠迴圈執行。以下的代碼就成功驅動了一個run loop:

BOOL isRunning = NO;      do {            isRunning = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDatedistantFuture]];     } while (isRunning);

1.3.3 Run loop同一時候也負責autorelease pool的建立和釋放
在使用手動的記憶體管理方式的項目中,會常常常使用到非常多自己主動釋放的對象,假設這些對象不能夠被即時釋放掉,會造成記憶體佔用量急劇增大。Run loop就為我們做了這種工作,每當一個執行迴圈結束的時候,它都會釋放一次autorelease pool,同一時候pool中的全部自己主動釋放類型變數都會被釋放掉。

1.3.4 Run loop的長處
一個run loop就是一個事件處理迴圈,用來不停的監聽和處理輸入事件並將其分配到對應的目標上進行處理。假設僅僅是想實現這個功能,你可能會想一個簡單的while迴圈不就能夠實現了嗎,用得著費老大勁來做個那麼複雜的機制?顯然,蘋果的架構設計師不是吃乾飯的,你想到的他們早就想過了。

首先,NSRunLoop是一種更加高明的訊息處理模式,他就高明在對訊息處理過程進行了更好的抽象和封裝,這樣才幹是的你不用處理一些非常瑣碎非常低層次的詳細訊息的處理,在NSRunLoop中每個訊息就被打包在input source或者是timer source(見後文)中了。

其次,也是非常重要的一點。使用run loop能夠使你的線程在有工作的時候工作,沒有工作的時候休眠,這能夠大大節省系統資源。

二、Run loop相關知識點

2.1輸入事件來源
Run loop接收輸入事件來自兩種不同的來源:輸入源(input source)和定時源(timer source)。兩種源都使用程式的某一特定的處理常式來處理到達的事件。圖-1顯示了run loop的概念結構以及各種源。

須要說明的是。當你建立輸入源。你須要將其分配給run loop中的一個或多個模式(什麼是模式,下文將會講到)。模式僅僅會在特定事件影響監聽的源。

大多數情況下,run loop執行在預設模式下。可是你也能夠使其執行在自己定義模式。若某一源在當前模式下不被監聽,那麼不論什麼其產生的訊息僅僅在run loop執行在其關聯的模式下才會被傳遞。
圖-1 Runloop的結構和輸入源類型

2.1.1輸入源(input source)
傳遞非同步事件,通常訊息來自於其它線程或程式。輸入源傳遞非同步訊息給對應的處理常式,並調用runUntilDate:方法來退出(線上程裡面相關的NSRunLoop對象調用)。

2.1.1.1基於port的輸入源
基於port的輸入源由核心自己主動發送。
Cocoa和Core Foundation內建支援使用port相關的對象和函數來建立的基於port的源。

比如,在Cocoa裡面你從來不須要直接建立輸入源。你僅僅要簡單的建立port對象,並使用NSPort的方法把該port加入到run loop。port對象會自己處理建立和配置輸入源。
在Core Foundation,你必須人工建立port和它的run loop源。我們能夠使用port相關的函數(CFMachPortRef,CFMessagePortRef。CFSocketRef)來建立合適的對象。

以下的範例展示了怎樣建立一個基於port的輸入源,將其加入到run loop並啟動:

voidcreatePortSource(){    CFMessagePortRef port = CFMessagePortCreateLocal(kCFAllocatorDefault, CFSTR("com.someport"),myCallbackFunc, NULL, NULL);    CFRunLoopSourceRef source =  CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, port, 0);    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);    while (pageStillLoading) {        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];        CFRunLoopRun();        [pool release];    }    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);    CFRelease(source);}

2.1.1.2自己定義輸入源
自己定義的輸入源須要人工從其它線程發送。
為了建立自己定義輸入源,必須使用Core Foundation裡面的CFRunLoopSourceRef類型相關的函數來建立。

你能夠使用回呼函數來配置自己定義輸入源。Core Fundation會在配置源的不同地方調用回呼函數,處理輸入事件。在源從run loop移除的時候清理它。
除了定義在事件到達時自己定義輸入源的行為,你也必須定義訊息傳遞機制。源的這部分執行在單獨的線程裡面。並負責在資料等待處理的時候傳遞資料給源並通知它處理資料。訊息傳遞機制的定義取決於你。但最好不要過於複雜。建立並啟動自己定義輸入源的示比例如以下:

voidcreateCustomSource(){    CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);    while (pageStillLoading) {        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];        CFRunLoopRun();        [pool release];    }    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);    CFRelease(source);}

2.1.1.3Cocoa上的Selector源
除了基於port的源。Cocoa定義了自己定義輸入源,同意你在不論什麼線程執行selector方法。和基於port的源一樣。執行selector請求會在目標線程上序列化。減緩很多線上程上同意多個方法easy引起的同步問題。

不像基於port的源。一個selector執行完後會自己主動從run loop裡面移除。
當在其它線程上面執行selector時,目標線程須有一個活動的run loop。對於你建立的線程。這意味著線程在你顯式的啟動run loop之前是不會執行selector方法的。而是一直處於休眠狀態。


NSObject類提供了相似例如以下的selector方法:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)argwaitUntilDone:(BOOL)wait modes:(NSArray *)array;

2.1.2定時源(timer source)
定時源在預設的時間點同步方式傳遞訊息。這些訊息都會發生在特定時間或者反覆的時間間隔。定時源則直接傳遞訊息給處理常式,不會馬上退出run loop。
須要注意的是,雖然定時器能夠產生基於時間的通知,但它並非即時機制。和輸入源一樣,定時器也和你的run loop的特定模式相關。

假設定時器所在的模式當前未被run loop監視,那麼定時器將不會開始直到run loop執行在對應的模式下。相似的,假設定時器在run loop處理某一事件期間開始。定時器會一直等待直到下次run loop開始對應的處理常式。

假設run loop不再執行,那定時器也將永遠不啟動。
建立定時器源有兩種方法。

方法一:NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:4.0                                                     target:self                                                   selector:@selector(backgroundThreadFire:) userInfo:nil                                                    repeats:YES];    [[NSRunLoop currentRunLoop] addTimer:timerforMode:NSDefaultRunLoopMode];方法二:[NSTimer scheduledTimerWithTimeInterval:10                                        target:self                                       selector:@selector(backgroundThreadFire:)                                       userInfo:nil                                       repeats:YES];

2.2 RunLoop觀察者
源是在合適的同步或非同步事件發生時觸發,而run loop觀察者則是在run loop本身執行的特定時候觸發。

你能夠使用run loop觀察者來為處理某一特定事件或是進入休眠的線程做準備。

你能夠將run loop觀察者和以下事件關聯:
1. Runloop入口
2. Runloop何時處理一個定時器
3. Runloop何時處理一個輸入源
4. Runloop何時進入睡眠狀態
5. Runloop何時被喚醒,但在喚醒之前要處理的事件
6. Runloop終止
和定時器相似,在建立的時候你能夠指定run loop觀察者能夠僅僅用一次或迴圈使用。若僅僅用一次。那麼在它啟動後。會把它自己從run loop裡面移除,而迴圈的觀察者則不會。定義觀察者並把它加入到run loop。僅僅能使用Core Fundation。

以下的範例示範了怎樣建立run loop的觀察者:

- (void)addObserverToCurrentRunloop{    // The application uses garbage collection, so noautorelease pool is needed.    NSRunLoop*myRunLoop = [NSRunLoop currentRunLoop];    // Create a run loop observer and attach it to the runloop.    CFRunLoopObserverContext  context = {0, self, NULL, NULL, NULL};   CFRunLoopObserverRef    observer =CFRunLoopObserverCreate(kCFAllocatorDefault,                                                              kCFRunLoopBeforeTimers, YES, 0, &myRunLoopObserver, &context);    if (observer)    {        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];       CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);    }}

當中。kCFRunLoopBeforeTimers表示選擇監聽定時器觸發前處理事件。後面的YES表示迴圈監聽。

2.3 RunLoop的事件隊列
每次執行run loop。你線程的run loop對會自己主動處理之前未處理的訊息,並通知相關的觀察者。詳細的順序例如以下:
通知觀察者run loop已經啟動
通知觀察者不論什麼即將要開始的定時器
通知觀察者不論什麼即將啟動的非基於port的源
啟動不論什麼準備好的非基於port的源
假設基於port的源準備好並處於等待狀態,馬上啟動;並進入步驟9。


通知觀察者線程進入休眠
將線程置於休眠直到任一以下的事件發生:
某一事件到達基於port的源
定時器啟動
Run loop設定的時間已經逾時
run loop被顯式喚醒
通知觀察者線程將被喚醒。
處理未處理的事件
假設使用者定義的定時器啟動,處理定時器事件並重新啟動run loop。

進入步驟2
假設輸入源啟動,傳遞對應的訊息
假設run loop被顯式喚醒並且時間還沒逾時,重新啟動run loop。進入步驟2
通知觀察者run loop結束。


由於定時器和輸入源的觀察者是在對應的事件發生之前傳遞訊息,所以通知的時間和實際事件發生的時間之間可能存在誤差。假設須要精確時間控制。你能夠使用休眠和喚醒通知來協助你校對實際發生事件的時間。


由於當你執行run loop時定時器和其它周期性事件常常須要被傳遞,撤銷run loop也會終止訊息傳遞。

典型的範例就是滑鼠路徑追蹤。

由於你的代碼直接擷取到訊息而不是經由程式傳遞,因此活躍的定時器不會開始直到滑鼠追蹤結束並將控制權交給程式。
Run loop能夠由run loop對象顯式喚醒。其它訊息也能夠喚醒run loop。

比如,加入新的非基於port的源會喚醒run loop從而能夠馬上處理輸入源而不須要等待其它事件發生後再處理。


從這個事件隊列中能夠看出:
①假設是事件到達,訊息會被傳遞給對應的處理常式來處理, runloop處理完當次事件後,run loop會退出。而無論之前預定的時間到了沒有。你能夠又一次啟動run loop來等待下一事件。
②假設線程中有須要處理的源,可是響應的事件沒有到來的時候,線程就會休眠等待對應事件的發生。這就是為什麼run loop能夠做到讓線程有工作的時候忙於工作。而沒工作的時候處於休眠狀態。

2.4什麼時候使用run loop
僅當在為你的程式建立輔助線程的時候,你才須要顯式執行一個run loop。Run loop是程式主線程基礎設施的關鍵區段。所以,Cocoa和Carbon程式提供了代碼執行主程式的迴圈並自己主動啟動run loop。IOS程式中UIApplication的run方法(或Mac OS X中的NSApplication)作為程式啟動步驟的一部分,它在程式正常啟動的時候就會啟動程式的主迴圈。相似的。RunApplicationEventLoop函數為Carbon程式啟動主迴圈。假設你使用xcode提供的模板建立你的程式,那你永遠不須要自己去顯式的調用這些常式。
對於輔助線程。你須要推斷一個run loop是否是必須的。假設是必須的。那麼你要自己配置並啟動它。

你不須要在不論什麼情況下都去啟動一個線程的run loop。

比方,你使用線程來處理一個預先定義的長時間執行的任務時。你應該避免啟動run loop。

Run loop在你要和線程有很多其它的互動時才須要。比方以下情況:
使用port或自己定義輸入源來和其它線程通訊
使用線程的定時器
Cocoa中使用不論什麼performSelector…的方法
使線程周期性工作
假設你決定在程式中使用run loop。那麼它的配置和啟動都非常easy。

和全部線程編程一樣。你須要計劃好在輔助線程退出線程的情形。

讓線程自然退出往往比強制關閉它更好。

學習參考連結:

  • http://blog.csdn.net/ztp800201/article/details/9240913
  • http://www.360doc.com/content/16/0302/04/31139390_538698652.shtml
  • http://www.cnblogs.com/cqb-learner/p/5859431.html

IOS-Run loop學習總結

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.