眼尖的讀者不知道有沒有發現,我們的 Hello World 專案執行起來 Android 跟 iOS 的顯示其實是不一樣的,iOS 是顯示 “Hello, iOS 15.5!“,而 Android 是顯示 “Hello, Android 33!”。
這個技術在當我們想要在不同平台使用不同的 library 非常實用,但如果無法理解就不能再需要的時候使用了,讓我們回頭看看 source code 了解 KMM 是怎麼做到的吧!
在 shared/src 這個資料夾下,大家會發現以下的結構:
jintinlin@Jintins-Air src % tree
.
├── androidMain
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── com.jintin.xxx
│ └── Platform.kt
├── commonMain
│ └── kotlin
│ └── com.jintin.xxx
│ └── Platform.kt
└── iosMain
└── kotlin
└── com.jintin.xxx
└── Platform.kt
在 commonMain、androidMain、iosMain 原來各有一個 Platform.kt,看來玄機就在這邊了,讓我們打開各別檔案看一下:
// commonMain
expect class Platform() {
val platform: String
}
// androidMain
actual class Platform actual constructor() {
actual val platform: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}
// iosMain
actual class Platform actual constructor() {
actual val platform: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}
這邊就會看到我們的主題 expect 跟 actual 了,expect 只提供協定,需要具體的 actual 來實作功能的細節,而二者必須透過一樣的名稱讓 compiler 建立連結綁定,所以如果沒有對應的 expect 或 actual 或是有多個相同物件都會造成 compile error,擷取官網的關係圖如下所示:
這邊也建議大家可以回頭在 androidMain 或 iosMain 改一下 code,看重新 run 起來會不會真的改變。
看到這邊是不是讓大家想到 interface 的設計呢?但 expect/actual 跟 interface 是有點不一樣的,如果是使用 interface 則上層的 interface 跟下層的實作是二個不同的物件,你必須要有具體實作的 reference 才能夠使用實作的功能,而 expect / actual 就如剛剛所說可以視為同個物件操作,所以當我們想把不同平台的實現分開的時候就會非常的適合。
Expect/Actual 使用上還有蠻多小細節,比如說,expect class 裡的所有 property/function 都必須為 expect,actual 裡可以有其他定義以外的 property/function,除了 class 之外,function、interface、property、top level function 也都可以使用 expect 來定義,具體使用起來真的是蠻方便的。
最後,我想說的是 KMM 作為 Kotlin 官方所推出的一套工具,跟一般的第三方工具是完全不一樣的,有點像 JxJava 跟 coroutine 的比較一樣,expect/actual 非常粗暴的直接從語法層面來支援這個功能,以我們作為 KMM 的使用者來說真是一大福音!