一个库在KMP应用中使用Swift代码的Kotlin Coroutines。
KMP和Kotlin Coroutines都很棒,但是它们有一些局限性。
最重要的限制是取消支持。
Kotlin悬浮函数的功能是带有完整处理程序的功能。
这使您可以轻松地从Swift代码中使用它们,但不支持取消。
笔记
尽管Swift 5.5将异步功能带来Swift,但它无法解决此问题。
对于与OBJC的互操作性,所有功能与完成处理程序的所有功能都可以称为异步功能。
这意味着从Swift 5.5开始,您的Kotlin悬挂功能看起来像Swift Async函数。
但这只是句法糖,因此仍然没有取消支持。
除了取消支持外,OBJC不支持协议上的仿制药。
因此,所有Flow接口都将失去其通用值类型,从而使它们难以使用。
该库解决了这两个限制?
库的最新版本使用Kotlin版本2.1.0 。
还提供了旧版本和/或预览Kotlin版本的兼容性版本:
| 版本 | 版本后缀 | 科特林 | KSP | Coroutines |
|---|---|---|---|---|
| 最新的 | 没有后缀 | 2.1.0 | 1.0.29 | 1.9.0 |
| 1.0.0-Alpha-37 | 没有后缀 | 2.0.21 | 1.0.25 | 1.9.0 |
| 1.0.0-Alpha-36 | 没有后缀 | 2.0.20 | 1.0.25 | 1.9.0 |
| 1.0.0-Alpha-35 | 没有后缀 | 2.0.20 | 1.0.24 | 1.8.1 |
| 1.0.0-Alpha-34 | 没有后缀 | 2.0.10 | 1.0.24 | 1.8.1 |
| 1.0.0-Alpha-33 | 没有后缀 | 2.0.0 | 1.0.24 | 1.8.1 |
| 1.0.0-Alpha-30 | 没有后缀 | 1.9.24 | 1.0.20 | 1.8.1 |
| 1.0.0-Alpha-28 | 没有后缀 | 1.9.23 | 1.0.20 | 1.8.0 |
| 1.0.0-Alpha-25 | 没有后缀 | 1.9.22 | 1.0.17 | 1.8.0 |
| 1.0.0-Alpha-23 | 没有后缀 | 1.9.21 | 1.0.16 | 1.7.3 |
| 1.0.0-Alpha-21 | 没有后缀 | 1.9.20 | 1.0.14 | 1.7.3 |
| 1.0.0-alpha-18 | 没有后缀 | 1.9.10 | 1.0.13 | 1.7.3 |
| 1.0.0-Alpha-17 | 没有后缀 | 1.9.0 | 1.0.12 | 1.7.3 |
| 1.0.0-alpha-12 | 没有后缀 | 1.8.22 | 1.0.11 | 1.7.2 |
| 1.0.0-alpha-10 | 没有后缀 | 1.8.21 | 1.0.11 | 1.7.1 |
| 1.0.0-Alpha-7 | 没有后缀 | 1.8.20 | 1.0.10 | 1.6.4 |
您可以从几个快速实现中进行选择。
根据实现的不同,您可以支持与iOS 9,MacOS 10.9,TVOS 9和WatchOS 3:3:
| 执行 | 迅速 | ios | macos | TVOS | 手表 |
|---|---|---|---|---|---|
| 异步 | 5.5 | 13.0 | 10.15 | 13.0 | 6.0 |
| 结合 | 5.0 | 13.0 | 10.15 | 13.0 | 6.0 |
| rxswift | 5.0 | 9.0 | 10.9 | 9.0 | 3.0 |
该库由Kotlin和Swift部分组成,您需要将其添加到项目中。
Kotlin零件可在Maven Central上找到,可以通过Cocoapods或Swift Package Manager安装Swift部分。
确保始终在所有库中使用相同的版本!
对于Kotlin,只需将插件添加到您的build.gradle.kts :
plugins {
id( " com.google.devtools.ksp " ) version " 2.1.0-1.0.29 "
id( " com.rickclephas.kmp.nativecoroutines " ) version " 1.0.0-ALPHA-38 "
}并确保选择进行实验@ObjCName注释:
kotlin.sourceSets.all {
languageSettings.optIn( " kotlin.experimental.ExperimentalObjCName " )
} SWIFT实现可通过Swift软件包管理器获得。
只需将其添加到您的Package.swift 。Swift文件:
dependencies: [
. package ( url : " https://github.com/rickclephas/KMP-NativeCoroutines.git " , exact : " 1.0.0-ALPHA-38 " )
] ,
targets: [
. target (
name : " MyTargetName " ,
dependencies : [
// Swift Concurrency implementation
. product ( name : " KMPNativeCoroutinesAsync " , package : " KMP-NativeCoroutines " ) ,
// Combine implementation
. product ( name : " KMPNativeCoroutinesCombine " , package : " KMP-NativeCoroutines " ) ,
// RxSwift implementation
. product ( name : " KMPNativeCoroutinesRxSwift " , package : " KMP-NativeCoroutines " )
]
)
]或通过转到File > Add Packages...并提供URL: https://github.com/rickclephas/KMP-NativeCoroutines.git ,将其添加到Xcode中。
笔记
Swift软件包的版本不应包含Kotlin版本后缀(例如-new-mm或-kotlin-1.6.0 )。
笔记
如果您只需要一个实现,则还可以使用带有后缀-spm-async , -spm-combine和-spm-rxswift SPM特定版本。
如果您使用Cocoapods,将以下一个或多个库添加到您的Podfile :
pod 'KMPNativeCoroutinesAsync' , '1.0.0-ALPHA-38' # Swift Concurrency implementation
pod 'KMPNativeCoroutinesCombine' , '1.0.0-ALPHA-38' # Combine implementation
pod 'KMPNativeCoroutinesRxSwift' , '1.0.0-ALPHA-38' # RxSwift implementation 笔记
Cocoapods的版本不应包含Kotlin版本后缀(例如-new-mm或-kotlin-1.6.0 )。
从Jetbrains Marketplace安装IDE插件以获取:
使用Swift的Kotlin Coroutines代码几乎与调用Kotlin代码一样容易。
只需在Swift中使用包装器函数即可获得异步功能,异步流,发布者或观察者。
该插件将自动为您生成必要的代码! ?
只需用@NativeCoroutines (或@NativeCoroutinesState )注释您的Coroutines声明即可。
您的Flow属性/功能获得本机版本:
import com.rickclephas.kmp.nativecoroutines.NativeCoroutines
class Clock {
// Somewhere in your Kotlin code you define a Flow property
// and annotate it with @NativeCoroutines
@NativeCoroutines
val time : StateFlow < Long > // This can be any kind of Flow
}该插件将为您生成此本机属性:
import com.rickclephas.kmp.nativecoroutines.asNativeFlow
import kotlin.native.ObjCName
@ObjCName(name = " time " )
val Clock .timeNative
get() = time.asNativeFlow()对于上面定义的StateFlow该插件还将生成此值属性:
val Clock .timeValue
get() = time.value如果有SharedFlow则插件将生成重播缓存属性:
val Clock .timeReplayCache
get() = time.replayCache使用StateFlow属性跟踪状态(例如在视图模型中)?
使用@NativeCoroutinesState注释:
import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState
class Clock {
// Somewhere in your Kotlin code you define a StateFlow property
// and annotate it with @NativeCoroutinesState
@NativeCoroutinesState
val time : StateFlow < Long > // This must be a StateFlow
}该插件将为您生成这些本机属性:
import com.rickclephas.kmp.nativecoroutines.asNativeFlow
import kotlin.native.ObjCName
@ObjCName(name = " time " )
val Clock .timeValue
get() = time.value
val Clock .timeFlow
get() = time.asNativeFlow()该插件还为您的注释悬浮函数生成本机版本:
import com.rickclephas.kmp.nativecoroutines.NativeCoroutines
class RandomLettersGenerator {
// Somewhere in your Kotlin code you define a suspend function
// and annotate it with @NativeCoroutines
@NativeCoroutines
suspend fun getRandomLetters (): String {
// Code to generate some random letters
}
}该插件将为您生成此本机功能:
import com.rickclephas.kmp.nativecoroutines.nativeSuspend
import kotlin.native.ObjCName
@ObjCName(name = " getRandomLetters " )
fun RandomLettersGenerator. getRandomLettersNative () =
nativeSuspend { getRandomLetters() }不幸的是,在Objective-C协议上不支持扩展功能/属性。
但是,通过一些迅速的魔法可以“克服”这种限制。
假设RandomLettersGenerator是一个interface ,而不是class ,我们可以进行以下操作:
import KMPNativeCoroutinesCore
extension RandomLettersGenerator {
func getRandomLetters ( ) -> NativeSuspend < String , Error , KotlinUnit > {
RandomLettersGeneratorNativeKt . getRandomLetters ( self )
}
} 当暂停功能和/或Flow声明暴露于OBJC/SWIFT时,编译器和IDE插件将产生警告,提醒您添加其中一种KMP-NativeCoroutines注释。
您可以在build.gradle.kts文件中自定义这些检查的严重性:
nativeCoroutines {
exposedSeverity = ExposedSeverity . ERROR
}或者,如果您对这些检查不感兴趣,请禁用它们:
nativeCoroutines {
exposedSeverity = ExposedSeverity . NONE
}异步实现提供了一些功能,以获得异步Swift函数和AsyncSequence s。
使用asyncFunction(for:)函数获得可以等待的异步函数:
import KMPNativeCoroutinesAsync
let handle = Task {
do {
let letters = try await asyncFunction ( for : randomLettersGenerator . getRandomLetters ( ) )
print ( " Got random letters: ( letters ) " )
} catch {
print ( " Failed with error: ( error ) " )
}
}
// To cancel the suspend function just cancel the async task
handle . cancel ( )或者,如果您不喜欢这些docatches,则可以使用asyncResult(for:)函数:
import KMPNativeCoroutinesAsync
let result = await asyncResult ( for : randomLettersGenerator . getRandomLetters ( ) )
if case let . success ( letters ) = result {
print ( " Got random letters: ( letters ) " )
}对于Unit返回函数,还有asyncError(for:)函数:
import KMPNativeCoroutinesAsync
if let error = await asyncError ( for : integrationTests . returnUnit ( ) ) {
print ( " Failed with error: ( error ) " )
} 对于Flow s,有asyncSequence(for:)函数获得AsyncSequence :
import KMPNativeCoroutinesAsync
let handle = Task {
do {
let sequence = asyncSequence ( for : randomLettersGenerator . getRandomLettersFlow ( ) )
for try await letters in sequence {
print ( " Got random letters: ( letters ) " )
}
} catch {
print ( " Failed with error: ( error ) " )
}
}
// To cancel the flow (collection) just cancel the async task
handle . cancel ( )联合实现提供了几个功能,可以为您的Coroutines代码获得AnyPublisher 。
笔记
这些函数创建了延期的AnyPublisher 。
这意味着每个订阅都会触发悬挂函数Flow或执行的收集。
笔记
您必须对返回的Cancellable s进行引用,否则该集合将立即取消。
对于您的Flow ,请使用createPublisher(for:)函数:
import KMPNativeCoroutinesCombine
// Create an AnyPublisher for your flow
let publisher = createPublisher ( for : clock . time )
// Now use this publisher as you would any other
let cancellable = publisher . sink { completion in
print ( " Received completion: ( completion ) " )
} receiveValue : { value in
print ( " Received value: ( value ) " )
}
// To cancel the flow (collection) just cancel the publisher
cancellable . cancel ( )您还可以使用createPublisher(for:)函数用于返回Flow的悬浮函数:
let publisher = createPublisher ( for : randomLettersGenerator . getRandomLettersFlow ( ) ) 对于悬浮函数,您应该使用createFuture(for:)函数:
import KMPNativeCoroutinesCombine
// Create a Future/AnyPublisher for the suspend function
let future = createFuture ( for : randomLettersGenerator . getRandomLetters ( ) )
// Now use this future as you would any other
let cancellable = future . sink { completion in
print ( " Received completion: ( completion ) " )
} receiveValue : { value in
print ( " Received value: ( value ) " )
}
// To cancel the suspend function just cancel the future
cancellable . cancel ( ) RXSWIFT实现提供了几个功能,可以为您的Coroutines代码获得Observable或Single功能。
笔记
这些函数创建了递延的Observable s和Single s。
这意味着每个订阅都会触发悬挂函数Flow或执行的收集。
对于您的Flow ,使用createObservable(for:)函数:
import KMPNativeCoroutinesRxSwift
// Create an observable for your flow
let observable = createObservable ( for : clock . time )
// Now use this observable as you would any other
let disposable = observable . subscribe ( onNext : { value in
print ( " Received value: ( value ) " )
} , onError : { error in
print ( " Received error: ( error ) " )
} , onCompleted : {
print ( " Observable completed " )
} , onDisposed : {
print ( " Observable disposed " )
} )
// To cancel the flow (collection) just dispose the subscription
disposable . dispose ( )您还可以使用createObservable(for:)函数用于返回Flow的悬浮函数:
let observable = createObservable ( for : randomLettersGenerator . getRandomLettersFlow ( ) ) 对于悬浮函数,您应该使用createSingle(for:)函数:
import KMPNativeCoroutinesRxSwift
// Create a single for the suspend function
let single = createSingle ( for : randomLettersGenerator . getRandomLetters ( ) )
// Now use this single as you would any other
let disposable = single . subscribe ( onSuccess : { value in
print ( " Received value: ( value ) " )
} , onFailure : { error in
print ( " Received error: ( error ) " )
} , onDisposed : {
print ( " Single disposed " )
} )
// To cancel the suspend function just dispose the subscription
disposable . dispose ( ) 您可以通过多种方式自定义生成的Kotlin代码。
不喜欢生成的属性/功能的命名吗?
在build.gradle.kts文件中指定自己的自定义后缀:
nativeCoroutines {
// The suffix used to generate the native coroutine function and property names.
suffix = " Native "
// The suffix used to generate the native coroutine file names.
// Note: defaults to the suffix value when `null`.
fileSuffix = null
// The suffix used to generate the StateFlow value property names,
// or `null` to remove the value properties.
flowValueSuffix = " Value "
// The suffix used to generate the SharedFlow replayCache property names,
// or `null` to remove the replayCache properties.
flowReplayCacheSuffix = " ReplayCache "
// The suffix used to generate the native state property names.
stateSuffix = " Value "
// The suffix used to generate the `StateFlow` flow property names,
// or `null` to remove the flow properties.
stateFlowSuffix = " Flow "
}有关更多控件,您可以使用NativeCoroutineScope注释提供自定义的CoroutineScope :
import com.rickclephas.kmp.nativecoroutines.NativeCoroutineScope
class Clock {
@NativeCoroutineScope
internal val coroutineScope = CoroutineScope (job + Dispatchers . Default )
}笔记
您的自定义Coroutine范围必须是internal或public 。
如果您不提供CoroutineScope ,则将使用默认范围,该范围定义为:
internal val defaultCoroutineScope = CoroutineScope ( SupervisorJob () + Dispatchers . Default )笔记
KMP-NativeCoroutines具有对KMP-ObservableViewModel的内置支持。
您的ViewModel内部的Coroutines(默认情况下)将使用ViewModelScope的CoroutineScope 。
使用NativeCoroutinesIgnore注释来告诉插件以忽略属性或函数:
import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesIgnore
@NativeCoroutinesIgnore
val ignoredFlowProperty : Flow < Int >
@NativeCoroutinesIgnore
suspend fun ignoredSuspendFunction () { }如果由于某种原因您想进一步改善Kotlin声明,则可以使用NativeCoroutinesRefined和NativeCoroutinesRefinedState注释。
这些将告诉插件将ShouldRefineInSwift注释添加到生成的属性/函数中。
笔记
目前,这需要宽阔的模块选择加入kotlin.experimental.ExperimentalObjCRefinement 。
例如,您可以将您的Flow属性完善到AnyPublisher属性:
import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesRefined
class Clock {
@NativeCoroutinesRefined
val time : StateFlow < Long >
}import KMPNativeCoroutinesCombine
extension Clock {
var time : AnyPublisher < KotlinLong , Error > {
createPublisher ( for : __time )
}
}