常駐線程是一種什麼體驗
簡說 RunLoop
關於 iOS RunLoop 網上很多文章都有介紹過, 很多技術面試官也會問關於 RunLoop 的相關知識. 我把自己工作中遇到的問題和總結的經驗分享出來(會做成一系列的文章), 也算是對自己的一個總結和沉澱, 歡迎大家交流.
網上的文章基本都是針對於Apple Developer Doc - Run Loops這篇來展開的, 所以建議大家認真的去通讀這篇文章, 並寫代碼驗證, 實踐.
可以簡單粗暴的這麼理解一下 RunLoop, 基於事件驅動的死循環(由內核來調度和管理的), 在需要處理事情的時候就出來干點事, 否則休眠待命. RunLoop 的核心是基於 的,其進入休眠時調用的函數是 mach_msg().
類似下面的代碼來說明一下:
說到這裡, 隨便提及一下, 學習過 Android 開發的同事應該和好理解 RunLoop 了, iOS 的 RunLoop 跟 Android 的 Looper 機制幾乎一樣, 只是不同的系統之間實現有差異罷了!
有興趣的朋友可以看一下我之前寫的文章Handler: 更新UI的方法.
今天跟大家分享如何在 iOS 中結合 RunLoop 和 machport 實現常駐線程, 先跟著實例走, 後續再去總結 RunLoop 的各種細節點.
神奇的 main
開發過 iOS 應用中的朋友, 對 再也熟悉不過了, 函數正是應用的入口函數.
我們將 代碼分開寫, 看看有什麼蛛絲馬跡可尋.
無論如何你也看不到日誌 的列印, 這說明 一直在呵護著 APP 的運行, 哈哈.
我們不妨再改一次, 如下:
再去運行 APP, 你會發現根本沒有讓 APP 運行起來, 再次說明沒有了 的呵護, APP 無法起死回生.
猜測在 函數中,開啟了和主線程相關的 RunLoop,使 不會返回一直在運行中,從而保證了程序的持續運行, 最大的功臣就是 RunLoop.
普通線程
一般我們開啟的線程在執行完任務後, 就會結束該線程. 除非你寫了類似下面的代碼:
或者
開啟一個線程
執行對應的 函數, 如下:
可以發現 很快就可以執行完成 (End Run).
子線程開啟 RunLoop
主線程是默認開啟 RunLoop 的即 mainRunLoop 是系統默認開啟的, 但是子線程中的 RunLoop 需要我們自己手動開啟.
關於為什麼子線程中需要手動開啟, 後續文章結合源碼給大家分析, 這裡暫時可以理解為獲取 RunLoop 對象是一種懶載入模式. 只不過主線程中, 系統幫我們開啟了, 然而子線程中需要我們手動開啟而已.
類似這樣:
在控制台可以看到輸出:
RunLoop 沒有任何輸入源(input source) 和定時器(timer), 這時即使開啟了 RunLoop 也不會讓其等待執行, 換句話說會立即結束當前的 RunLoop.
既然這樣我們給子線程的 RunLoop 添加源或者定時器即可. 這裡以添加 NSPort 為例.
再次運行, 你會發現 這個 Log 是不會列印出來的. 對應當前的 RunLoop 也有了源和定時器, 如圖所示:
關於定時器和 RunLoop 的結合, 下篇再分享.
現在有這樣一個需求, 需要在指定的線程中執行某項任務, 顯然使用上面的方法來滿足需求, 下面進入今天的正題.
驗證常駐線程
一定到 這個詞, 就知道是能夠讓該線程隨時待命, 保證其不掛掉.
iOS 中默認就有個主線程即 , 我們的 UI 線程指的就是主線程, 一般都是在主線程中操作 UI, 從某個角度來說, 主線程就是一個常駐線程.
我們開啟其他線程, 目的是為了非同步完成一些任務, 這些任務一般都比較耗時, 如果放在主線程當中完成這些任務就會導致主線程的卡頓, 用戶體驗極其差.
說了這麼多, 也許你會問, 為什麼要常駐線程呢?
頻繁的創建和銷毀線程,會造成資源(主要是內存)的浪費, 我們為什麼不讓頻繁使用的子線程常駐在內存中, 想用的時候就用, 不用的時候讓他休眠呢?!
上面已經使用 RunLoop 來實現了讓線程長時間存活而不被銷毀了.
用 來模擬在指定線程中再次執行任務(runAnyTime)的方法.
對應上面的 實現即可, 你會發現在當前頁面每次點擊屏幕都會執行 .
附錄
代碼的完整實現
常駐線程, 可以參考具體的注釋.
MZCreatePermanentThreadController.m
參考文檔
Toll-Free Bridging
Run Loops
TAG:ITMan |