iOS Runloop學習筆記

躍然發表於2015-02-27

一、* what is run loop *

1、A run loop is an abstraction that (among other things) provides a mechanism to handle system input sources (sockets, ports, files, keyboard, mouse, timers, etc).

Each NSThread has its own run loop, which can be accessed via the currentRunLoop method.

In general, you do not need to access the run loop directly, though there are some (networking) components that may allow you to specify which run loop they will use for I/O processing.

A run loop for a given thread will wait until one or more of its input sources has some data or event, then fire the appropriate input handler(s) to process each input source that is “ready.”.

After doing so, it will then return to its loop, processing input from various sources, and “sleeping” if there is no work to do.

runloop.png

2、首先考慮這個問題:你的Cocoa程式大部分的時間什麼都沒做,更具體點,是在等待輸入。然而,一旦你觸控螢幕,相應的事件被觸發,就可能會執行你的一段事件處理程式碼。同理,socket中返回一些資料,或者計時器觸發等也是一樣的情況。而且更重要的是,一旦觸發事件的程式碼執行完,程式就會回到等待狀態。在很多情況下,程式碼執行的時間要遠小於程式等待輸入的時間。

我認為run loop就是較好的利用了這個事實的一種機制。一個run loop就是跑在單個執行緒上進行事件處理的迴圈。你在run loop上註冊輸入源,並指定當這些源有輸入時應該執行的程式碼。當特定的源上有輸入時,run loop就會執行對應的程式碼,然後繼續等待下一個輸入事件。如果在run loop正在執行處理程式碼時,另外一個源的輸入到了,run loop會在執行完正當前的處理後處理這個輸入事件。好處是雖然你不知道具體的輸入順序,但你知道它們最終會一個接一個地被序列處理。這就是說你不會遇到多執行緒的問題,這也是run loop非常有用的原因。

RunLoop這個東西,其實我們一直在用,但一直沒有很好地理解它,或者甚至沒有知道它的存在。RunLoop可以說是每個執行緒都有的一個物件,是用來接受事件和分配任務的loop。永遠不要手動建立一個runloop,它是跟隨著每個執行緒的。一個RunLoop接收兩種source的事件:input source和timer source。同時必須知道的是,input source,runloop是非同步交付的,而timer source是同步交付的。每個runloop都有一個RunLoop Modes,代表它以何種方式執行。

我們為什麼從來沒有感覺到runloop的存在呢,是因為當程式啟動,系統預設幫我們啟動了一個主執行緒的runloop,並且一直在執行,直到程式退出。而使用者建立的子執行緒,runloop是需要手動啟動的,所以線上程裡啟動timer或者呼叫performSelector: withObject:afterDelay:inModes: 是需要啟動runloop的。

RunLoop從字面上看是執行迴圈的意思,這一點也不錯,它確實就是一個迴圈的概念,或者準確的說是執行緒中的迴圈。 本文一開始就提到有些程式是一個圈,這個圈本質上就是這裡的所謂的RunLoop,就是一個迴圈,只是這個迴圈里加入很多特性。
首先迴圈體的開始需要檢測是否有需要處理的事件,如果有則去處理,如果沒有則進入睡眠以節省CPU時間。 所以重點便是這個需要處理的事件,在RunLoop中,需要處理的事件分兩類,一種是輸入源,一種是定時器,定時器好理解就是那些需要定時執行的操作,輸 入源分三類:performSelector源,基於埠(Mach port)的源,以及自定義的源。程式設計的時候可以新增自己的源。

二、* Run loop的使用 *

預設情況下,iPhone上的所有觸控事件都會被main run loop放在佇列裡等待處理,所以你不需要對UI元件做額外的事情,而其他輸入源需要一些額外的編碼。比如在run loop上schedule一個NSInputStream,你需要像下面這樣:

[iStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

在上面的程式碼中,一旦iStream有輸入資料,就會執行self的stream:handleEvent的方法。而且這個stream可以是任意型別的輸入源,包括socket.

另外,timer物件也可以被schedule在run loop上,比如:

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(doStuff) userInfo: nil repeats:YES];

上面的程式碼把計時器schedule到當前的run loop上,每2秒就會呼叫self的doStuff方法。

三、* 不適用run loop的情況 *

那什麼時候不適合使用run loop呢?根據run loop的特點,輸入事件會一個接一個的被序列處理,那麼如果一個事件的處理需要的時間特別長的話,就會導致在這個事件處理完之前,app無法響應別的輸入事件。在這種情況下,新開一個執行緒處理更合適。 然而,大部分情況下,我們的程式碼處理螢幕、socket或者計時器事件都非常快,這時使用main run loop處理起來更簡單,也更安全。

參考連結:

  1. Threading Programming Guide
  2. Threading Programming Guide翻譯
  3. Objective-C之run loop詳解
  4. iOS中的RunLoop
  5. Understanding NSRunLoop
  6. Run loop和Thread
  7. iOS關於RunLoop和Timer
  8. iOS多執行緒的初步研究(三)– NSRunLoop

相關文章