本文就是 Coroutine 的最後一篇了,當然圍繞著 Coroutine 一定還有很多主題可以分享,但局限於鐵人賽篇幅,今天我們就著重在於怎麼切 Thread 以及 Android / iOS 上的運用。
如果學習過 Android 開發一定對 Context 這個詞很熟悉,但 Context
同時也非常難用中文表示,直翻叫做上下文,代表著一個讓我們可以得到某些能力的環境參數,而 CoroutineContext
就是在 Coroutine 裡使用的 Context,這可以拿來做什麼呢?其中一個就是指定 Thread。
Dispatchers
是 Coroutine 指定 Thread 執行的一個常數,而它也是一種 CoroutineContext
,常用有以下幾種:
Dispatchers.Default
: Default 的 Dispatcher,通常跟 CPU 的個數相同Dispatchers.Main
: 將會執行在 Main thread 上Dispatchers.IO
: 將會執行在 IO thread 上而不管是之前介紹的 launch
或是 runBlocking
其實都可以再多加上一個 CoroutineContext
讓我們修改當前的 Context,所以如果想要讓 lambda block 裡的程式執行在指定的 Dispatcher 下只要類似如下呼叫就可以了:
launch(Dispatchers.IO) {
println("World!") // this will run in IO thread
}
而如果不想建立新的 Coroutine,只是想切換執行的 Thread,也可以使用 withContext
來切換,範例如下:
withContext(Dispatchers.IO) {
println("World!") // this will run in IO thread
}
值得一提的是 withContext
會把最後一行的執行結果回傳,所以當我們把 function 都使用 withContext
指定好執行的 Thread 的時候,就可以平鋪直敘的寫非同步代碼了,範例如下:
suspend fun apiCall(): String = withContext(Dispatchers.IO) {
// call api
return@withContext ""
}
suspend fun displayUI(data: String) = withContext(Dispatchers.Main) {
// display UI
}
val result = apiCall() // IO Thread
displayUI(result) // Main Thread
由於 Android 也可以使用 Kotlin 直接寫,所以使用上其實跟我們之前所介紹的沒什麼二樣,只是 Google 有提供 Android 使用的 CoroutineScope
:viewModelScope
以及 lifecycleScope
,分別可以在 ViewModel 以及 UI 層使用,就可以更專注在商業邏輯上而不用煩惱建立變數以及管理何時需要呼叫 cancel
了。
而 iOS 的使用相對來說就是個大問題,首先 Swift 裡面根本就不會有任何 Kotlin 的 CoroutineScope
可以使用,而且 suspend 在 Swift 裡也不會有特殊功能,那 KMM 要怎麼處理這個問題呢?
我們可以試試看在 shared module 裡建立一個 public 的 suspend function,然後從 iOS 這邊看這個 function 的定義,就會發現 suspend function 在 iOS 會自動轉變成 callback 的形式,多了一個名叫 completionHandler
的 callback,來通知結果或是異常,範例如下:
// shared
suspend fun getUser(userName: String): User
// iOSMain
getUser(userName: "Jintin", completionHandler: { user, error in
...
})
但實際上跑起來會出現以下的問題:
Trying to access top level value not marked as @ThreadLocal or @SharedImmutable from non-main thread
這是因為之前提到的 Kotlin Native 在 multi-Thread 的環境需要些額外的設定,但這些在新的 memory manager 之中是不需要特別處理的,可以在 gradle.properties
加上以下的 config 就可以正常了:
kotlin.native.binary.memoryModel=experimental
如果是持續關注本系列文章的讀者,應該會有印象我們在第四天其實就有提到這個 Kotlin/Native 的 memory manager 吧,讓我們一起期待 KMM 能盡快順利的升上 Beta 以及 Stable 囉~