一個庫在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 )
}
}