KMM Library中如何在Swift中实现Kotlin密封类的Switch匹配
我正在开发一个KMM库(而非KMM应用),会打包成.xcframework/.framework供Xcode项目使用,以及.aar供Android项目使用。
我需要实现UIState状态(Loading、Success、Error),于是创建了如下Kotlin密封类:
sealed class UIState<out T> { object Loading : UIState<Nothing>() data class Success<T>(val data: T) : UIState<T>() data class Error(val message: String) : UIState<Nothing>() }
接着用Coroutine编写了API调用函数:
suspend fun getDotaHeroesWithSealedClass(): UIState<List<DotaHero>> = when (httpClient.get("https://api.opendota.com/api/heroes").status.value) { in 200..299 -> { UIState.Success(httpClient.get("https://api.opendota.com/api/heroes").body()) } else -> { UIState.Error("An error has occured") } }
执行./gradlew assembleXCFramework命令生成.xcframework并关联到Xcode项目后,发现该函数的返回类型被识别为UIState
switch uiState { case .loading: case .success(let data): case .error(let errorMessage): }
要让Kotlin密封类在Swift中正确识别并支持模式匹配,按以下步骤调整:
1. 给密封类添加序列化支持(可选但关键)
Kotlin/Native对带泛型的密封类的Swift映射需要序列化支持来确保类型信息正确传递。先在库的build.gradle.kts中添加序列化插件和依赖:
plugins { kotlin("multiplatform") kotlin("plugin.serialization") version "和你的Kotlin版本匹配" } sourceSets { commonMain.dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.0") } }
然后给密封类及内部类加上@Serializable注解:
import kotlinx.serialization.Serializable @Serializable sealed class UIState<out T> { @Serializable object Loading : UIState<Nothing>() @Serializable data class Success<T>(val data: T) : UIState<T>() @Serializable data class Error(val message: String) : UIState<Nothing>() }
2. 优化API调用并修正泛型映射
原代码中重复发起了两次HTTP请求,先优化这一点;另外Kotlin的List默认映射为Swift的NSArray,要让它转为原生Array,确保泛型类型无歧义:
suspend fun getDotaHeroesWithSealedClass(): UIState<List<DotaHero>> { val response = httpClient.get("https://api.opendota.com/api/heroes") return when (response.status.value) { in 200..299 -> UIState.Success(response.body()) else -> UIState.Error("An error has occurred") } }
3. 配置Kotlin/Native的Swift互操作参数
在build.gradle.kts的Kotlin配置块中,添加Swift泛型支持的编译参数,确保密封类被正确生成为Swift可匹配的枚举结构:
kotlin { // 你的iOS目标配置 iosX64() iosArm64() iosSimulatorArm64() sourceSets { // 你的依赖配置 } targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> { binaries.framework { baseName = "你的库名称" isStatic = true // 静态库配置,按需调整 // 添加泛型支持编译参数 freeCompilerArgs += "-Xobjc-generics" } } }
4. 确保DotaHero类的兼容性
DotaHero数据类需要是可序列化的,避免使用Swift不兼容的特性(比如无默认值的参数、复杂嵌套泛型),这样才能在Swift中被识别为原生模型类型。
完成所有调整后,重新执行./gradlew assembleXCFramework生成框架。此时在Swift项目中,getDotaHeroesWithSealedClass的返回类型会正确识别为UIState<[DotaHero]>,并且可以使用你期望的switch分支匹配逻辑。
内容的提问来源于stack exchange,提问作者Mohammad Azri Khairuddin

