Translate

bloggerads內文

2017年11月30日 星期四

IOS-初探RunLoop

前言:在IOS上也接觸了快3年了,但對比較更深層的結構上也想更深入了解,回頭想想
我們都只是在用蘋果提供的SDK,也許有些人未想它是如何產生的,但以我目前待的專案公司而言只要東西快速做好上架,你不太會去理解App的執行過程,但為了讓自己更了解,所以就去爬文,其實我們常用UITableview以及UITimer等會使用這個,也可以發覺滿多在GitHub上的第三方套件,幾乎都會有這東西在這裡面,這麼厲害,那這次就來做個RunLoop筆記吧.

1.什麼是RunLoop
一開始聽這個字面上就覺得是循環阿, 去看Apple官網後,其實RunLoop的存在其實就是為執行緒而存在的。執行緒的作用就是執行一個特定的任務,但是預設情況下執行緒執行完任務後就不能再次執行任務,這是因為預設情況下執行是沒有開啟RunLoop的。如果開啟RunLoop之後,執行緒執行完任務之後,會一直等待,直到再次接受到任務,接續執行任務。執行緒銷毀前,會先釋放這個執行緒所對應的RunLoop。

2.RunLoop可以做什麼?
1.維持程式的持續運行,保持執行緒的持續運行。

2.處理應用程式中的各種事件(比如觸碰事件,定時器事件,選擇事件)

3.節省CPU資源,提高程式性能:該做事時做事,該休息時休息

3.RunLoop與執行緒的關係
每條執行都有唯一的一個與之對應的RunLoop對象,主執行的Runloop系統已經自動創建好了,子執行的RunLoop需要手動創建,RunLoop在第一次獲取時由系統自動創建,在執行結束時銷毀,如果想給子執行創建RunLoop,不能直接alloc&init,只要調用獲取當前執RunLoop方法即可,系統會自動放回當前執行的RunLoop,如果當前執行沒有RunLoop,系統會自動創建。

4.RunLoop相關5類
1.CFRunLoopRef:RunLoop對象
2.CFRunLoopModeRef:RunLoop運行模式。
3.CFRunLoopSoruceRef:事件源(輸入源)
4.CFRunLoopTimerRef:基於時間的觸發器。
5.CFRunLoopObserverRef:觀察者,能夠監聽RunLoop的狀態改變


到這邊有一點基本概念了,那麼每一個類又能代表什麼呢?接下來就每個一一介紹吧.

1.CFRunLoopModeRef
一個RunLoop對象包含多個Mode模式,每個Mode又包含多個source/Timer/Observer RunLoop運行時,只能指定一個Mode,這個Mode又稱之為CurrentMode,然後RunLoo就執行CurrentMode中的source/Timer/Observer,如果需要切換模式,只能退出RunLoop,再重新指定一個模式進入,這樣做是為了分隔開不同組的來源/定時/觀察,讓其不受影響

系統預設註冊了5個模式:

NSDefaultRunLoopMode(kCFRunLoopDefaultMode):App的預設模式,通常主執行緒實現這個模式下運行

UITrackingRunLoopMode:界面跟踪模式,用於界面控件(滾動型,的tableView等等)追踪觸摸滑動,保證界面滑動時不受其他模式影響

UIInitializationRunLoopMode:在剛啟動應用程序是進入的第一個模式,啟動完成後就不再使用

GSEventReceiveRunLoopMode:接收系統事件的內部模式,通常用不到

NSRunLoopCommonMode:這是一個佔位的模式,不是一種真正的模式,(可以看成模式組,預設情況下包括了NSDefaultRunLoopMode,UITrackingRunLoopMode)兩種模式。

2.CFRunLoopTimer Ref
CFRunLoopTimerRef基本上說的就是的NSTimer,它受RunLoop的模式影響,而當scrollView滾動的時候,當前的MainRunLoop是處理UITrackingRunLoopMode的模式下,在這個模式下,是不會處理NSDefaultRunLoopMode的消息(因為RunLoop模式不一樣),要想在scrollView滾動的同時也接受其它runloop的消息,我們需要改變兩者之間的runloopmode
ex:
[[NSRunLoop currentRunLoop] addTimer:timer forModeNSRunLoopCommonModes];

簡單的說就是NSTimer不會開啟新的進程,只是在Runloop裡註冊了一下,Runloop每次循環時都會檢測這個計時器,看是否可以觸發。當Runloop在A模式下,而計時器註冊在B模式時就無 去檢測這個計時器,所以需要把NSTimer也註冊到A模式,這樣就可以被檢測到

再舉個例子:如有做過在UITableView的cell 放入timer,此時我們滾動會發現,竟然不會計時了?原因就是上敘所說,必須加入到模式裡才行.


3.CFRunLoopSoruceRef
根據官網所說內容
以前的分法:
Port-Based Sources:基於端口的事件源:監聽程序響應的端口,基於端口事件是由系統內自動發送的.
Custom Input Sources: 自定義輸入源:監聽自定義事件源,而自定義的輸入源是需要人工從其他執行緒發送
Cocoa Perfrom Selector Source: selector事件源

現在的分法:
Source0:非基於Prot(端口)的,是用戶主動觸發的事件
Source1:基於Prot(端口)的,通過內核和其他執行緒相互發送消息

恩....什麼意思阿?相信大家在下斷點時,都會看到這樣圖示!



有沒有覺得原來是這意思的感覺.XD

4.CFRunLoopObserverRef
CFRunLoopObserverRef:觀察者對象,可以監聽RunLoop的狀態

RunLoop狀態:

4.1 kCFRunLoopEntry 即將進入runLoop

4.2 kCFRunLoopBeforeTimers 即將處理Timer

4.3 kCFRunLoopBeforeSources 即將處理source(事件源)

4.4 kCFRunLoopBeforeWaiting 即將進入休眠

4.5 kCFRunLoopAfterWaiting 即將從休眠中醒來

4.6 kCFRunLoopExit 即將退出runLoop

ex:
//創建一個CFRunLoopObserverRef
/*
第一個參數: CFRunLoopObserverRef(觀察者)分配記憶體空間方式
第二個參數: 監聽那些狀態 kCFRunLoopAllActivities(監聽所有狀態)
第三個參數: 是否每次都要監聽
第四個參數: 優先級
第五個參數: 回調函數
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES,0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
      // observer監聽對象
      //activity Runloop當前狀態
});
/*
第一個參數: 為那個執行緒下的RunLoop添加CFRunLoopObserverRef(觀察者)
第二個參數: 需要添加的CFRunLoopObserverRef(觀察者)
第三個參數: 把監聽添加到RunLoop那個模式中
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

//記得記憶體管理,因為Core Foundation不在ARC管理範圍內
//帶有Create、Copy、Retain等字眼的函數,創建出來的對象,都需要在最後做一次release
//銷毀對象函數:CFRelease對象
CFRelease(observer);

5.RunLoop處理邏輯
如果RunLoop中沒有Timer或source,RunLoop就會立刻退出

每次運行RunLoop,RunLoop會自動處理之前未處理的消息,並通知相關觀察者.具體順序如下:

1.通知觀察者RunLoop已經啟動

2.通知觀察者即將開始啟動定時器

3.通知觀察者即將啟動非基於端口的事件源

4.啟動任何準備好的非基於端口的事件源

5.如果基於端口的事件源準備好並處於等待得狀態,立即啟動.並進入步驟9

6.通知觀察者線程進入休眠

7.將執行緒置於休眠直到任意下面的事件發生:

  • 某一事件到達基於端口的源
  • 定時器啟動
  • RunLoop設置的時間已經超時.(系統底層會給RunLoop設置一個超時時間,源碼中設置的是:9999999999.0)
  • RunLoop被手動喚醒

8.通知觀察者線程將被喚醒.

9.處理未處理的事件

  • 如果用戶定義的定時器啟動,處理定時器事件並重啟RunLoop.進入步驟2
  • 如果事件源啟動,傳遞相應的消息
  • 如果RunLooop被顯示喚醒而且時間還沒超時,重啟RunLoop.進入步驟2
10.通知觀察者RunLoop結束.

終於打完了..(攤

接下來我要去想下個主題了....


沒有留言:

張貼留言