用于平台 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 usedactual 类型别名
如果某个平台已经具有所需的完全匹配的类,请使用 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 反馈 — 无需本地设置。
此课程中的所有课时
- KMP 项目结构:commonMain、androidMain、iosMain
- 用于平台 API 的 expect/actual 机制
- 共享存储库层与用例层
- 使用 Koin 在 KMP 中进行依赖注入