Runloop隨手記

Runloop是iOS開發的一個難點,需要不斷體會才能真正理解。

一個runloop有幾個要素組成,一個是runloop模式,一個是source,一個是observer。runloop模式是需要監視的source和observer的集合,runloop運行期間,只有和該模式相關的source纔會被監視並允許傳遞事件,也只允許相關的observer會被通知runloop的進程。 source分input source和timer source。

input source:

基於端口的輸入源

自定義輸入源

Cocoa執行selector的源


timer source:

timer source(定時源)

務必記住一點,輸入源和觀察者都是依賴於當前runloop的模式的。模式不對,是不會觸發它們工作的。


timer創建

NSTimer的運行是依賴於runloop的。可以通過以下三種方式創建:

使用 scheduledTimerWithTimeInterval:invocation:repeats: 或者scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 這兩個類方法創建一個timer並把它指定到一個默認的runloop模式中

  使用 timerWithTimeInterval:invocation:repeats: 或者 timerWithTimeInterval:target:selector:userInfo:repeats:這兩個類方法創建一個timer的對象,不把它知道那個到run loop. (當創建之後,你必須手動的調用NSRunLoop下對應的方法 addTimer:forMode: 去將它制定到一個runloop模式中.)

  使用 initWithFireDate:interval:target:selector:userInfo:repeats: 方法分配並創建一個NSTimer的實例 (當創建之後,你必須手動的調用NSRunLoop下對應的方法addTimer:forMode: 去將它制定到一個runloop模式中.)

(以上引用自http://www.cnblogs.com/ios-wmm/archive/2012/08/24/2654779.html)

前面兩種方式創建的timer可能不能滿足我們的需要,這時候,我們還得執行addTimer:forMode:加到正確的runloop中去。


timer啓動

timer的啓動有兩個條件,一是當前爲匹配的runloop mode,二是fire date到來。不管如何你可以先調用fire來等待runloop進入匹配的模式。或者可以設置fireDate,這都是類似的。設置不同的fireDate,在不同時期會有不同的意義。

time  setFireDate:[NSDate  distantFuture]] 很顯然這個一直不會fire,如果正在運行中的話,就相當於暫停了。

[time setFireDate:[NSDate  distanPast]] 這個是已經fire了。

[timer setFireDate:[NSDate date]] 這個表示繼續,和fire其實是一樣的。和上面的沒有什麼區別。

除了設置fireDate,timer創建的時候設定的timeinterval也是另一種觸發方式。


timer的銷燬

timer要從runloop中銷燬,要使用invalidate方法:

if ([scrollTimer isValid] == YES) {
        [scrollTimer invalidate];
        scrollTimer = nil;
}

這裏還有個可能會碰到的問題,滾動tableview時候,NSTimer似乎卡住了。原因是當前的runloop的mode發生了改變,運行在默認模式上的source沒有被觸發。可參考http://blog.csdn.net/jasonblog/article/details/7854693。

core foundation版本:

RunLoopRef runLoop = CFRunLoopGetCurrent(); CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL}; CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0,                                         &myCFTimerCallback, &context);  CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes); 

說完timer,就要說說input source。自定義的先不管,先說基於端口和Cocoa的selector源。首先他們都是iOS進程間通信的方式。現在我們很少使用前者,推薦使用後者。

NSMachPort方式下,NSPort有3個子類,NSSocketPort、NSMessagePort、NSMachPort,但在iOS下只有NSMachPort可用。使用的方式爲接收線程中註冊NSMachPort,在另外的線程中使用此port發送消息,則被註冊線程會收到相應消息,然後最終在主線程裏調用某個回調函數。(http://blog.sina.com.cn/s/blog_7815a31f0101ea0n.html)

配置一個NSMachPort相當的複雜。

首先要在當前線程創建一個Port,並設置其代理,加入runloop中。然後分配一個新的線程,在上面調用另一個類的方法,很關鍵的一點是要把這個Port作爲參數傳入。

代理要實現handlePortMessage:方法或者handleMachMessage:方法。參數是NSPortMessage,可以從它的msgid方法中獲取message類型,並作出相應的處理。

再回到被調用的那個類的方法中,這時候傳入了一個Port,被調用的類有責任向該Port發送message來和調用線程通信,因此要在該調用方法中調用sendCheckinMessage:
諸如之類的方法,參數是NSPort類型。當然這個方法也有義務保持自己的runloop。

sendCheckinMessage:方法中,你同樣要創建自己的Port,並設置代理,實現代理方法,將Port加入運行循環。關鍵的一點是要創建NSPortMessage對象,連接這兩個Port。創建完之後要設置msgid,並通過sendBeforeDate:方法發送出去。

配置一個NSMessagePort對象 (略)

Core Foundation版本(略)

以上詳細代碼可查看http://mobile.51cto.com/hot-403083_2.htm

這時候似乎是拋出創建自定義輸入源的時候。自定義輸入源也是一個大坑,也很複雜。簡單來說,你需要創建source,並把它安裝到runloop,然後發送一個signal通知源並喚醒runloop。source的創建依賴於一個context,context中指定你關心事件的回調函數。這些事件包括scheduleRoutine,performRouting和cancelRouting等。在scheduleRouting中你可以把這些source的引用保存起來便於調度。performRouting中應該是執行source的主要的任務。cancelRouting中,如果這些source被管理中的話,這個時候就可以將其從管理隊列中移除。分段講解的代碼詳見官方文檔。



Runloop的啓動

runloop有三種啓動方式:

無條件的,不推薦

設置超時

特定模式

後面兩種是建議的做法,當然它們不是互斥的,一個超時的也可以是在某種模式下的。


退出runloop

退出有兩種方式:

超時自退

通知停止,只能使用CFRunLoopStop來實現。

儘管已出runloop中的所有輸入源和定時源也可能導致runloop退出,但是文檔多次強調這是不可取的,因爲系統會動態加入一些source。

另外NSRunloop是非線程安全的,而CFRunloop是線程安全的。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章