天天看點

iOS--關于RunLoop的一些總結

先從一個比較常見的問題入手: 

5     NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1
 6                                               target:self
 7                                             selector:@selector(printMessage:)
 8                                             userInfo:nil
 9                                              repeats:YES];
      

我們這樣加了一個定時器, 一般情況下是沒有問題,  但是當遇到scrollview或者tableView滾動時, 定時器就停止了,  這其實就是runloop的mode在做怪. runloop可以了解為cocoa下的一種消息循環機制,用來處理各種消息事件. 我們不需要主動建立一個runloop, 一般一個子線程自帶有一個runloop.

在開啟一個NSTimer實質上是在目前的runloop中注冊了一個新的事件源,而當scrollView滾動的時候,目前的MainRunLoop是處于UITrackingRunLoopMode的模式下,在這個模式下,是不會處理NSDefaultRunLoopMode的消息(因為RunLoop Mode不一樣),要想在scrollView滾動的同時也接受其它runloop的消息,我們需要改變兩者之間的 .  簡單的說就是NSTimer不會開啟新的程序,隻是在Runloop裡注冊了一下,Runloop每次loop時都會檢測這個timer,看是否可以觸發。當Runloop在A mode,而timer注冊在B mode時就無法去檢測這個timer,是以需要把NSTimer也注冊到A mode,這樣就可以被檢測到。是以我們需要加上這句話: 

1 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];      

NSRunLoopCommonModes, 這種模式意思就是基本包括所有的模式, UITrackingRunLoopMode, NSConnectionReplyMode等等, 而且目前的RunLoop是主線程, 我們可以肯定主線程肯定注冊了很多種模式, 這些模式會發出很多事件, 是以會立即傳回. (

官方文檔:Objects added to a run loop using this value as the mode are monitored by all run loop modes that have been declared as a member of the set of “common" modes; see the description of CFRunLoopAddCommonMode for details.

)

好了這個問題已經解決了, 說下runloop吧: run loop,顧名思義,就是一個循環,你的線程在這裡開始,并運作事件處理程式來響應輸入事件。你的代碼要有實作循環部分的控制語句,換言之就是要有while或for語句。一個run loop就是一個事件處理循環,用來不停的調配工作以及處理輸入事件。使用run loop的目的是使你的線程在有工作的時候工作,沒有的時候休眠。

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

底層的CFRunLoop --> 封裝好的NSRunloop, 經常接觸的還是NSRunLoop, 下面以NSRunLoop為主 二、RunLoopMode NSDefaultRunLoopMode 這是大多數操作中使用的模式。

NSConnectionReplyMode 該模式用來監控NSConnection對象。你通常不需要在你的代碼中使用該模式。

NSModalPanelRunLoopMode Cocoa使用該模式來辨別用于modal panel(模态面闆)的事件。

NSEventTracking(UITrackingRunLoopMode) Cocoa使用該模式來處理使用者界面相關的事件。

NSRunLoopCommonModes 這是一組可配置的通用模式。将input sources與該模式關聯則同時也将input sources與該組中的其它模式進行了關聯。對于Cocoa應用,該模式預設的包含了default,modal以及event tracking模式。

  (一個常見的問題就是,主線程中一個NSTimer添加在default mode中,當界面上有一些scroll view的滾動頻繁發生導緻run loop運作在UItraking mode中,進而這個timer沒能如期望那般的運作。是以,我們就可以把這個timer加到NSRunLoopCommonModes中來解決(iOS中)。)

來看看這張經典的圖檔

iOS--關于RunLoop的一些總結

其中Input source是一些異步的事件,比如port,selector等,這個會讓runUntilDate:跳出(當然指的是非主線程中的runloop)。Timer source是同步的,一個timer結束後,在重複時間後或者手動fire後才會再一次調用。

三. 一些常用的方法:

  + (NSRunLoop *)currentRunLoop

    如果調用的線程中沒有runloop,那麼将會建立一個并傳回

  + (NSRunLoop *)mainRunLoop

    傳回主線程的runloop

  - (void)acceptInputForMode:(NSString *)mode beforeDate:(NSDate *)limitDate

    運作loop一次或者直到limitDate。如果沒有input sources加入到這個loop,那麼馬上傳回;否則一直運作到limitDate,或者接口到一個input source然後傳回。

  - (void)addPort:(NSPort *)aPort forMode:(NSString *)mode

  - (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode

    port和timer都可以添加到多個mode中

  - (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(id)anArgument

    取消所有mode中的perform select,argument必須跟指定調用時候的一樣

  - (void)cancelPerformSelectorsWithTarget:(id)target

  - (NSString *)currentMode

    如果run loop沒有運作,那麼傳回nil

  - (CFRunLoopRef)getCFRunLoop

  - (NSDate *)limitDateForMode:(NSString *)mode

    下一次運作的時間,如果沒有指定的mode上沒有input source,傳回nil

  - (void)performSelector:(SEL)aSelector target:(id)target argument:(id)anArgument order:(NSUInteger)order modes:(NSArray *)modes

order值越低優先級越高

  - (void)removePort:(NSPort *)aPort forMode:(NSString *)mode

  - (void)run

    在default mode下無限運作loop,但是如果沒有任何input source,會立即傳回。手動移除所有已知的inout source并不能保證run loop停止運作,因為系統可能會添加一些input source。

  - (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate

    運作input source一次,為指定mode的input阻塞直到date的時間。如過沒有input source,立即傳回并傳回NO。

  - (void)runUntilDate:(NSDate *)limitDate

  如果沒有input source,立即傳回。否則在limitDate到來之前,不停的循環。

再詳細的就看文檔吧

在來看看這張圖檔

iOS--關于RunLoop的一些總結

它說明了使用者對ui的操作實際上是一種port,會放到一個隊列中傳到loop,然後由loop交給主線程處理。loop就是一個循環,接受event,傳遞,繼續。主線程是另一個循環,負責事件的處理與界面的顯示。當然這兩者關系複雜。