【发布时间】:2021-08-03 11:51:03
【问题描述】:
我正在尝试创建一个多平台库,它需要一些特定于平台/应用程序的依赖项才能正常运行。作为一种解决方案,我想在 kotlin 代码中定义接口,并让宿主应用程序实现这些接口,并在使用库时将它们提供给库。
这个库将使用协程多线程在其他线程上做某些事情,它需要使用这些线程中的这些依赖项。
这是 kotlin native 的内存模型出现并咬我的地方,所以我找到了使用 Stately 的独立状态作为我的依赖项的解决方案,但这并没有按预期工作。从 iOS 单元测试中运行代码时,完全用 kotlin 代码编写,一切正常,但是当我尝试从实际的 iOS Swift 代码中调用它时,它变得很奇怪。
考虑下面的多平台示例代码,其中我为依赖创建者和依赖定义了接口,它们都需要在 swift 中实现。使用它,我可以将创建的依赖项放在 IsolateState 中,这样我就可以从多个线程访问它。
interface DependencyCreator {
fun create(): Dependency
}
interface Dependency {
fun foo()
}
class SomeClient(
someDependencyCreator: DependencyCreator
) {
init {
println("is dependency creator frozen: ${someDependencyCreator.isFrozen}")
}
private val dependency: IsolateState<Dependency> = IsolateState {
val theDependency = someDependencyCreator.create()
println("is the dependency frozen? ${theDependency.isFrozen}")
return@IsolateState theDependency
}
suspend fun doFoo() = withContext(Dispatchers.Default) {
delay(100)
dependency.access { it.foo() }
}
}
我编写了以下单元测试代码,我使用 ios 目标 (./gradlew iosTest -i) 运行:
@Test
fun testFrozenDependency() {
val dependencyCreator = object : DependencyCreator {
override fun create(): Dependency {
return object : Dependency {
override fun foo() {
println("I foo'd")
}
}
}
}
val client = SomeClient(dependencyCreator)
runBlocking {
client.doFoo()
}
assertTrue(true)
}
这个单元测试的结果是:
is dependency creator frozen: false
is the dependency frozen? false
I foo'd
但是,当我尝试在 iOS 上完全执行此操作时,它不起作用。这是我的 iOS 代码:
class iOSDependency : Dependency {
func foo() {
print("foo'd from ios")
}
}
class iOSDependencyCreator : DependencyCreator {
func create() -> Dependency {
return iOSDependency()
}
}
class ViewModel : ObservableObject {
var client: SomeClient = SomeClient(someDependencyCreator: iOSDependencyCreator())
}
当我运行这段代码(ViewModel 被实例化)时,我得到以下结果:
is dependency creator frozen: true
is the dependency frozen? true
Function doesn't have or inherit @Throws annotation and thus exception isn't propagated from Kotlin to Objective-C/Swift as NSError.
It is considered unexpected and unhandled instead. Program will be terminated.
Uncaught Kotlin exception: kotlin.IllegalStateException: Mutable state shouldn't be frozen
at 0 library 0x0000000109adcf8f kfun:kotlin.Throwable#<init>(kotlin.String?){} + 95 (/Users/teamcity2/buildAgent/work/11ac87a349af04d5/runtime/src/main/kotlin/kotlin/Throwable.kt:23:37)
at 1 library 0x0000000109ad68cd kfun:kotlin.Exception#<init>(kotlin.String?){} + 93 (/Users/teamcity2/buildAgent/work/11ac87a349af04d5/runtime/src/main/kotlin/kotlin/Exceptions.kt:23:44)
at 2 library 0x0000000109ad6b3d kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 93 (/Users/teamcity2/buildAgent/work/11ac87a349af04d5/runtime/src/main/kotlin/kotlin/Exceptions.kt:34:44)
at 3 library 0x0000000109ad70ad kfun:kotlin.IllegalStateException#<init>(kotlin.String?){} + 93 (/Users/teamcity2/buildAgent/work/11ac87a349af04d5/runtime/src/main/kotlin/kotlin/Exceptions.kt:70:44)
at 4 library 0x0000000109b9276e kfun:co.touchlab.stately.isolate.StateHolder#<init>(1:0;co.touchlab.stately.isolate.StateRunner){} + 702 (/Users/runner/work/Stately/Stately/stately-isolate/src/nativeCommonMain/kotlin/co/touchlab/stately/isolate/Platform.kt:17:19)
at 5 library 0x0000000109b91694 kfun:co.touchlab.stately.isolate.createState$lambda-0#internal + 324 (/Users/runner/work/Stately/Stately/stately-isolate/src/commonMain/kotlin/co/touchlab/stately/isolate/IsoState.kt:49:30)
at 6 library 0x0000000109b918c3 kfun:co.touchlab.stately.isolate.$createState$lambda-0$FUNCTION_REFERENCE$2.invoke#internal + 163 (/Users/runner/work/Stately/Stately/stately-isolate/src/commonMain/kotlin/co/touchlab/stately/isolate/IsoState.kt:49:28)
at 7 library 0x0000000109b92162 kfun:co.touchlab.stately.isolate.BackgroundStateRunner.stateRun$lambda-1#internal + 354 (/Users/runner/work/Stately/Stately/stately-isolate/src/nativeCommonMain/kotlin/co/touchlab/stately/isolate/BackgroundStateRunner.kt:15:24)
at 8 library 0x0000000109b7f988 _ZN6Worker19processQueueElementEb + 3624
at 9 library 0x0000000109b7eb46 _ZN12_GLOBAL__N_113workerRoutineEPv + 54
at 10 libsystem_pthread.dylib 0x00007fff61167109 _pthread_start + 148
at 11 libsystem_pthread.dylib 0x00007fff61162b8b thread_start + 15
如您所见,与单元测试不同,提供的 swift 实现在“进入”多平台代码时被冻结。
目前我很茫然。如何让您的多平台库依赖于宿主应用程序提供的依赖项,并使用来自多个线程的这些依赖项(使用 IsolateState,因此应该解决所有冻结内存的问题)?是否有一些技巧可以不冻结 swift 生成的类实例?
我使用了以下版本:
- kotlin 多平台 1.4.32
- kotlinx-coroutines-core 版本 1.5.0-native-mt
【问题讨论】:
-
这可能有助于澄清它,但这些是不同的问题。这个问题中的代码看起来是正确的,但我怀疑这是用 Swift 扩展 Kotlin 的副作用。
标签: swift kotlin kotlin-coroutines kotlin-multiplatform kotlin-native