Keyword: expect/actual
有的時候,在不同平台上,功能的實作有平台上的限制,而這些限制並不是可以單單靠程式碼而去同共用的,例如藍芽裝置,在Android和iOS上規格和官方需求就完全不同.所以如果需要使用到藍芽裝置收集資訊,並當成資料層的物件,在物理上是不可能共用的.
好在KMM在限制我們使用shared層共享時,也考慮到了這點,因為平台獨特特性而需要有不同的實作時,經過特別的語法expect/actual,就能夠獨立實作.
讓我們實際來嘗試看看吧.首先我們在SharedMain底下,建立一個檔案,叫做KMMLogger,裡面有兩個部分.一個是針對底層所建立的expect class,另外一個是給上層使用的class.可以注意到所有的上層交互的位置都是這個Class,也因此不會受到expect class的影響.
internal expect class Logger() {
fun info(tag: String, message: String)
fun debug(tag: String, message: String)
fun warn(tag: String, message: String)
fun error(tag: String, message: String)
}
object KMMLogger {
private val logger = Logger()
fun i(tag: String, message: String) {
logger.info(tag, message)
}
fun d(tag: String, message: String) {
logger.debug(tag, message)
}
fun w(tag: String, message: String) {
logger.warn(tag, message)
}
fun e(tag: String, message: String) {
logger.error(tag, message)
}
}
然後,在iosMain同樣的路徑下,建立剛剛的expect的實作,這次使用的語法是actual來說明這是expect的實作.
注意,一定要放在完全相同路徑底下,如果你建立了一個log資料夾去存放剛剛建立的檔案,那在iosMain和AndroidMain實作的時候也需要這個package路徑.
你可以藉由package路徑有沒有完全相同來驗證這件事,別擔心,如果路徑不同在expect的區塊便會找不到實作,而標記為錯誤.
iosMain內的實作如下:
internal actual class Logger {
actual fun info(tag: String, message: String) {
print("Log level: info || Tag = $tag || Message = $message")
}
actual fun debug(tag: String, message: String) {
print("Log level: debug || Tag = $tag || Message = $message")
}
actual fun warn(tag: String, message: String) {
print("Log level: warning || Tag = $tag || Message = $message")
}
actual fun error(tag: String, message: String) {
print("Log level: error || Tag = $tag || Message = $message")
}
}
先暫時印出來Log即可
而androidMain如下
import android.util.Log
internal actual class Logger {
actual fun info(tag: String, message: String) {
Log.i(tag, message)
}
actual fun debug(tag: String, message: String) {
Log.d(tag, message)
}
actual fun warn(tag: String, message: String) {
Log.w(tag, message)
}
actual fun error(tag: String, message: String) {
Log.e(tag, message)
}
}
要使用的時候,就使用對上層的KMMLogger,進行優雅的呼叫,例如昨天的iOS Ktor部分
class CafeResponseItemViewModel: ObservableObject {
@Published var cafeResponseItemList = [CafeResponseItem]()
private let repository: DataRepository
private let kmmLogger : KMMLogger //增加Logger的引用
init(repository: DataRepository) {
self.repository = repository
}
func fetch() {
repository.fetchCafesFromNetwork(cityName:"taipei"){ result , error in
if let result = result{
self.kmmLogger.info(“Ktor Result”,result)//呼叫Logger
self.cafeResponseItemList = result
}
}
}
}
對於底層的實作,我們利用expect 去限制,而對於平台使用,就如同一般物件使用,藉由這個分離,使用的部分不用了解實作,所以就避開了各平台實作不一致所帶來各種問題.
有點像interface與implement當作物件之間的約束關係,而expect 與actual即是平台之間的約束關係.
明天我們,會來介紹這幾天使用到的Coroutine,Coroutine是Kotlin處理工作的絕活之一.