0Pricing
Kotlin Academy · 课时

用于平台 API 的 expect/actual 机制

在公共代码中声明预期声明,并为各个平台提供实际实现。

用于平台 API 的 expect/actual 机制 是 CoddyKit 上的免费 Kotlin Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Kotlin Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Kotlin Academy 课程共包含 4 节课。

expect/actual 模式

expect 在 commonMain 中声明接口契约。每个平台都提供一个 actual 实现。编译器会确保所有目标平台都有对应的 actual。

// commonMain:
expect fun platformName(): String

// androidMain:
actual fun platformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}"

// iosMain:
actual fun platformName(): String = UIDevice.currentDevice.systemName()

expect class

expect class 在 commonMain 中声明一个类及其接口。每个平台都提供完整的实现。

// commonMain:
expect class DateTime {
    fun format(pattern: String): String
    val timestamp: Long
}

// androidMain:
actual class DateTime {
    actual val timestamp = System.currentTimeMillis()
    actual fun format(pattern: String): String =
        java.text.SimpleDateFormat(pattern).format(java.util.Date(timestamp))
}

expect object

expect object 声明一个单例及其契约。它适合用于平台特定的工具,例如日志记录、设置或分析功能。

// commonMain:
expect object Logger {
    fun debug(msg: String)
    fun error(msg: String, cause: Throwable? = null)
}

// androidMain:
actual object Logger {
    actual fun debug(msg: String) = android.util.Log.d("App", msg)
    actual fun error(msg: String, cause: Throwable?) =
        android.util.Log.e("App", msg, cause)
}

顶层 expect val / fun

顶层 expect val 和 expect fun 简单易用,非常适合无状态的平台工具。

// commonMain:
expect val isDebugBuild: Boolean
expect fun generateUUID(): String

// androidMain:
actual val isDebugBuild: Boolean = BuildConfig.DEBUG
actual fun generateUUID(): String = java.util.UUID.randomUUID().toString()

// iosMain:
actual val isDebugBuild: Boolean = false // or a native flag
actual fun generateUUID(): String = NSUUID().UUIDString()

带默认实现的 expect fun

从 Kotlin 1.9+ 开始,expect fun 可以通过 expect fun f() { } 提供默认实现。各平台可以覆盖默认实现,也可以直接使用它。

// commonMain (Kotlin 1.9+):
expect fun log(msg: String) {
    println("[DEFAULT] $msg") // used if no actual overrides
}

// androidMain:
actual fun log(msg: String) = android.util.Log.d("KMP", msg)

// iosMain (uses default):
// no actual needed — default is used

actual 类型别名

如果某个平台已经具有所需的完全匹配的类,请使用 actual typealias 指向该类,而不是对它进行包装。

// commonMain:
expect class AtomicInt {
    fun get(): Int
    fun incrementAndGet(): Int
}

// androidMain / jvmMain:
actual typealias AtomicInt = java.util.concurrent.atomic.AtomicInteger

// iosMain:
actual class AtomicInt {
    private var value = 0
    actual fun get() = value
    actual fun incrementAndGet() = ++value
}

expect interface(间接模式)

为了获得更大的灵活性,请在 commonMain 中声明接口,并且只对创建平台实例的工厂使用 expect/actual。

// commonMain:
interface FileStorage {
    suspend fun readFile(path: String): ByteArray
    suspend fun writeFile(path: String, data: ByteArray)
}
expect fun createFileStorage(): FileStorage

// androidMain:
actual fun createFileStorage(): FileStorage = AndroidFileStorage()

// iosMain:
actual fun createFileStorage(): FileStorage = IosFileStorage()

使用 expect/actual 进行测试

在 commonTest 中测试共享接口。实际实现会在平台特定的测试任务中运行(Android 上运行 testDebugUnitTest,iOS 上运行 iosX64Test)。

// commonTest:
import kotlin.test.Test
import kotlin.test.assertNotNull
class PlatformTest {
    @Test
    fun testUUID() {
        val uuid = generateUUID()
        assertNotNull(uuid)
        println("UUID: $uuid")
    }
}

常见问题

每个声明的 expect 都必须在所有已配置的目标平台中拥有对应的 actual,否则构建会失败。缺少 actual 的问题会在编译时被发现。

// If you add a new target (e.g., jvmMain), you must add:
// actual fun platformName(): String = "JVM"
// Otherwise:
// error: Expected function platformName has no actual declaration in module :shared for JVM
fun main() { println("Missing actuals = compile error") }

Expect 注解类

expect annotation class 允许进行平台特定的注解映射,例如,将公共的 @Parcelize 类注解映射到 Android Parcelize 插件。

// commonMain:
expect annotation class CommonParcelize()

// androidMain:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
actual annotation class CommonParcelize

// iosMain:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
actual annotation class CommonParcelize

实际应用:Keychain/偏好设置

一个典型的实际应用场景是 expect/actual 安全存储接口:Android 使用 EncryptedSharedPreferences,iOS 使用 Keychain。

// commonMain:
expect class SecureStorage {
    fun save(key: String, value: String)
    fun get(key: String): String?
}

// androidMain:
actual class SecureStorage {
    private val prefs = /* EncryptedSharedPreferences */ mapOf<String,String>()
    actual fun save(key: String, value: String) { /* ... */ }
    actual fun get(key: String): String? = null // simplified
}

快速检查

如果一个 expect 声明没有针对某个目标平台的 actual,编译时会发生什么?

总结

expect 在 commonMain 中声明接口。actual 提供平台实现。如果平台已经具有所需的类型,请使用 actual typealias。缺少 actual 的问题会在编译时被发现,不会造成运行时意外。

常见问题解答

「用于平台 API 的 expect/actual 机制」课时是免费的吗?

是的 — 「用于平台 API 的 expect/actual 机制」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Kotlin Academy 课程的其余内容,请升级到 CoddyKit PRO。 Kotlin Academy 课程共包含 4 节课。

「用于平台 API 的 expect/actual 机制」这节课中我会学到什么?

在公共代码中声明预期声明,并为各个平台提供实际实现。 你通过在浏览器中直接运行的动手代码来练习 Kotlin Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Kotlin Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Kotlin Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「用于平台 API 的 expect/actual 机制」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Kotlin Academy 课中编写并运行代码吗?

能。每节 Kotlin Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. KMP 项目结构:commonMain、androidMain、iosMain
  2. 用于平台 API 的 expect/actual 机制
  3. 共享存储库层与用例层
  4. 使用 Koin 在 KMP 中进行依赖注入
← 返回 Kotlin Academy