Eine Bibliothek zur Verwendung von Kotlin Coroutinen aus Swift -Code in KMP -Apps.
Sowohl KMP als auch Kotlin -Coroutinen sind erstaunlich, aber zusammen haben sie einige Einschränkungen.
Die wichtigste Einschränkung ist die Stornierungsunterstützung.
Kotlin -Suspend -Funktionen sind Swift als Funktionen mit einem Fertigstellungshandler ausgesetzt.
Auf diese Weise können Sie sie problemlos aus Ihrem Swift -Code verwenden, unterstützt jedoch keine Stornierung.
Notiz
Während Swift 5.5 asynchronisierte Funktionen auf Swift bringt, löst es dieses Problem nicht.
Für die Interoperabilität mit OBJC kann alle Funktionen mit einem Fertigstellungshandler wie eine asynchronisierende Funktion bezeichnet werden.
Dies bedeutet, dass Sie mit Swift 5.5 angefangen haben. Ihre Kotlin -Suspend -Funktionen sehen wie Swift -Async -Funktionen aus.
Aber das ist nur syntaktischer Zucker, daher gibt es immer noch keine Stornierungsunterstützung.
Neben der Stornierungsunterstützung unterstützt OBJC Generika nicht auf Protokollen.
Alle Flow -Schnittstellen verlieren also ihren generischen Werttyp, der es schwierig macht, sie zu bedienen.
Diese Bibliothek löst beide Einschränkungen?
Die neueste Version der Bibliothek verwendet Kotlin Version 2.1.0 .
Kompatibilitätsversionen für ältere und/oder Vorschau -Kotlin -Versionen sind ebenfalls verfügbar:
| Version | Versionsuffix | Kotlin | KSP | Coroutinen |
|---|---|---|---|---|
| letzte | Kein Suffix | 2.1.0 | 1.0.29 | 1.9.0 |
| 1.0.0-alpha-37 | Kein Suffix | 2.0.21 | 1.0.25 | 1.9.0 |
| 1.0.0-alpha-36 | Kein Suffix | 2.0.20 | 1.0.25 | 1.9.0 |
| 1.0.0-alpha-35 | Kein Suffix | 2.0.20 | 1.0.24 | 1.8.1 |
| 1.0.0-alpha-34 | Kein Suffix | 2.0.10 | 1.0.24 | 1.8.1 |
| 1.0.0-alpha-33 | Kein Suffix | 2.0.0 | 1.0.24 | 1.8.1 |
| 1.0.0-alpha-30 | Kein Suffix | 1.9.24 | 1.0.20 | 1.8.1 |
| 1.0.0-alpha-28 | Kein Suffix | 1.9.23 | 1.0.20 | 1.8.0 |
| 1.0.0-alpha-25 | Kein Suffix | 1.9.22 | 1.0.17 | 1.8.0 |
| 1.0.0-alpha-23 | Kein Suffix | 1.9.21 | 1.0.16 | 1.7.3 |
| 1.0.0-alpha-21 | Kein Suffix | 1.9.20 | 1.0.14 | 1.7.3 |
| 1.0.0-alpha-18 | Kein Suffix | 1.9.10 | 1.0.13 | 1.7.3 |
| 1.0.0-alpha-17 | Kein Suffix | 1.9.0 | 1.0.12 | 1.7.3 |
| 1.0.0-alpha-12 | Kein Suffix | 1.8.22 | 1.0.11 | 1.7.2 |
| 1.0.0-alpha-10 | Kein Suffix | 1.8.21 | 1.0.11 | 1.7.1 |
| 1.0.0-alpha-7 | Kein Suffix | 1.8.20 | 1.0.10 | 1.6.4 |
Sie können aus einigen schnellen Implementierungen wählen.
Abhängig von der Implementierung können Sie so niedrig wie iOS 9, macOS 10.9, tvos 9 und watchos 3 unterstützen:
| Durchführung | Schnell | iOS | macos | tvos | Watchos |
|---|---|---|---|---|---|
| Asynchron | 5.5 | 13.0 | 10.15 | 13.0 | 6.0 |
| Kombinieren | 5.0 | 13.0 | 10.15 | 13.0 | 6.0 |
| Rxswift | 5.0 | 9.0 | 10.9 | 9.0 | 3.0 |
Die Bibliothek besteht aus einem Kotlin- und Swift -Teil, den Sie Ihrem Projekt hinzufügen müssen.
Das Kotlin -Teil ist auf Maven Central erhältlich und das Swift -Teil kann über Cocoapods oder den Swift -Paketmanager installiert werden.
Verwenden Sie immer die gleichen Versionen für alle Bibliotheken!
Für Kotlin fügen Sie einfach das Plugin zu Ihrem 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 "
} und stellen Sie sicher, dass Sie sich für die experimentelle @ObjCName -Annotation entscheiden:
kotlin.sourceSets.all {
languageSettings.optIn( " kotlin.experimental.ExperimentalObjCName " )
} Die Swift -Implementierungen sind über den Swift -Paketmanager erhältlich.
Fügen Sie es einfach zu Ihrem Package.swift hinzu.
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 " )
]
)
] Oder fügen Sie es in Xcode hinzu, indem Sie zu File > Add Packages... und die URL angeben: https://github.com/rickclephas/KMP-NativeCoroutines.git .
Notiz
Die Version für das SWIFT -Paket sollte nicht das Kotlin -Versionsuffix (z. B. -new-mm oder -kotlin-1.6.0 ) enthalten.
Notiz
Wenn Sie nur eine einzelne Implementierung benötigen, können Sie auch die SPM -spezifischen Versionen mit Suffixe -spm-async , -spm-combine und -spm-rxswift verwenden.
Wenn Sie Cocoapods verwenden, fügen Sie Ihrem Podfile einen oder mehrere der folgenden Bibliotheken hinzu:
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 Notiz
Die Version für Cocoapods sollte das Kotlin -Versionsuffix (z. B. -new-mm oder -kotlin-1.6.0 ) nicht enthalten.
Installieren Sie das IDE -Plugin vom JetBrains Marketplace, um zu erhalten:
Die Verwendung Ihres Kotlin Coroutines -Codes von Swift ist fast so einfach wie das Aufrufen des Kotlin -Code.
Verwenden Sie einfach die Wrapper -Funktionen in Swift, um asynchronische Funktionen, asyncstreams, Verlage oder Observablen zu erhalten.
Das Plugin generiert automatisch den erforderlichen Code für Sie! ?
Anmerkende Annotieren Sie einfach Ihre Coroutiner -Erklärungen mit @NativeCoroutines (oder @NativeCoroutinesState ).
Ihre Flow -Eigenschaften/-funktionen erhalten eine native Version:
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
}Das Plugin generiert diese native Eigenschaft für Sie:
import com.rickclephas.kmp.nativecoroutines.asNativeFlow
import kotlin.native.ObjCName
@ObjCName(name = " time " )
val Clock .timeNative
get() = time.asNativeFlow() Für den oben definierten StateFlow generiert das Plugin auch diese Werteigenschaft:
val Clock .timeValue
get() = time.value Bei einem SharedFlow würde das Plugin eine Replay -Cache -Eigenschaft generieren:
val Clock .timeReplayCache
get() = time.replayCache Verwenden von StateFlow -Eigenschaften, um den Status zu verfolgen (wie im Ansichtsmodell)?
Verwenden Sie stattdessen die @NativeCoroutinesState -Annotation:
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
}Das Plugin generiert diese nativen Eigenschaften für Sie:
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()Das Plugin generiert auch native Versionen für Ihre kommentierten Suspend -Funktionen:
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
}
}Das Plugin erzeugt diese native Funktion für Sie:
import com.rickclephas.kmp.nativecoroutines.nativeSuspend
import kotlin.native.ObjCName
@ObjCName(name = " getRandomLetters " )
fun RandomLettersGenerator. getRandomLettersNative () =
nativeSuspend { getRandomLetters() }Leider werden Erweiterungsfunktionen/Eigenschaften auf objektiven C-Protokollen nicht unterstützt.
Diese Einschränkung kann jedoch mit einer schnellen Magie "überwunden" werden.
Unter der Annahme, dass RandomLettersGenerator eine interface anstelle einer class ist, können wir Folgendes tun:
import KMPNativeCoroutinesCore
extension RandomLettersGenerator {
func getRandomLetters ( ) -> NativeSuspend < String , Error , KotlinUnit > {
RandomLettersGeneratorNativeKt . getRandomLetters ( self )
}
} Wenn Suspend-Funktionen und/oder Flow OBJC/Swift ausgesetzt sind, wird das Compiler- und IDE-Plugin eine Warnung erzeugen, die Sie daran erinnert, einen der Annotationen von KMP-NativCoroutinen hinzuzufügen.
Sie können die Schwere dieser Überprüfungen in Ihrer Datei build.gradle.kts anpassen:
nativeCoroutines {
exposedSeverity = ExposedSeverity . ERROR
}Oder, wenn Sie nicht an diesen Schecks interessiert sind, deaktivieren Sie sie:
nativeCoroutines {
exposedSeverity = ExposedSeverity . NONE
} Die asynchronisierende Implementierung bietet einige Funktionen, um asynchronen Swift -Funktionen und AsyncSequence s zu erhalten.
Verwenden Sie die asyncFunction(for:) -Funktion, um eine asynchronisierte Funktion zu erhalten, die erwartet werden kann:
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 ( ) Oder wenn Sie diese Stimmbezug nicht mögen, können Sie das asyncResult(for:) Funktion verwenden:
import KMPNativeCoroutinesAsync
let result = await asyncResult ( for : randomLettersGenerator . getRandomLetters ( ) )
if case let . success ( letters ) = result {
print ( " Got random letters: ( letters ) " )
} Für Unit Rückgabefunktionen gibt es auch den asyncError(for:) Funktion:
import KMPNativeCoroutinesAsync
if let error = await asyncError ( for : integrationTests . returnUnit ( ) ) {
print ( " Failed with error: ( error ) " )
} Für Flow S gibt es die asyncSequence(for:) Funktion, um eine AsyncSequence zu erhalten:
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 ( ) Die Kombinatimplementierung bietet ein paar Funktionen, um einen AnyPublisher für Ihren Coroutines -Code zu erhalten.
Notiz
Diese Funktionen erzeugen aufgeschobene AnyPublisher s.
Dies bedeutet, dass jedes Abonnement die Erfassung des Flow oder der Ausführung der Suspend -Funktion auslöst.
Notiz
Sie müssen einen Hinweis auf die zurückgegebenen Cancellable haben, da sonst die Sammlung sofort storniert wird.
Für Ihren Flow verwenden Sie den createPublisher(for:) Funktion:
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 ( ) Sie können auch den createPublisher(for:) Funktion für Suspend -Funktionen, die einen Flow zurückgeben:
let publisher = createPublisher ( for : randomLettersGenerator . getRandomLettersFlow ( ) ) Für die Suspend -Funktionen sollten Sie die createFuture(for:) -Funktion verwenden:
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 ( ) Die RXSWIFT -Implementierung bietet ein paar Funktionen, um eine Observable oder Single für Ihren Coroutines -Code zu erhalten.
Notiz
Diese Funktionen erzeugen aufgeschobene Observable und Single s.
Dies bedeutet, dass jedes Abonnement die Erfassung des Flow oder der Ausführung der Suspend -Funktion auslöst.
Für Ihren Flow verwenden Sie den createObservable(for:) Funktion:
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 ( ) Sie können auch den createObservable(for:) Funktion für Suspend -Funktionen verwenden, die einen Flow zurückgeben:
let observable = createObservable ( for : randomLettersGenerator . getRandomLettersFlow ( ) ) Für die Suspend -Funktionen sollten Sie das createSingle(for:) Funktion verwenden:
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 ( ) Es gibt eine Reihe von Möglichkeiten, wie Sie den generierten Kotlin -Code anpassen können.
Mögen Sie die Benennung der generierten Eigenschaften/Funktionen nicht?
Geben Sie Ihre eigenen benutzerdefinierten Suffixe in Ihrem build.gradle.kts -Datei an:
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 "
} Für mehr Kontrolle können Sie ein benutzerdefiniertes CoroutineScope mit der NativeCoroutineScope -Annotation bereitstellen:
import com.rickclephas.kmp.nativecoroutines.NativeCoroutineScope
class Clock {
@NativeCoroutineScope
internal val coroutineScope = CoroutineScope (job + Dispatchers . Default )
}Notiz
Ihr kundenspezifischer Coroutine -Umfang muss entweder internal oder public sein.
Wenn Sie kein CoroutineScope bereitstellen, wird der Standardbereich verwendet, der definiert ist als:
internal val defaultCoroutineScope = CoroutineScope ( SupervisorJob () + Dispatchers . Default )Notiz
KMP-NATIVECOROUTINES bietet integrierte Unterstützung für KMP-beobachtbare ViewModel.
Coroutinen in Ihrem ViewModel werden (standardmäßig) das CoroutineScope aus dem ViewModelScope verwenden.
Verwenden Sie die NativeCoroutinesIgnore -Annotation, um dem Plugin eine Eigenschaft oder Funktion zu ignorieren:
import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesIgnore
@NativeCoroutinesIgnore
val ignoredFlowProperty : Flow < Int >
@NativeCoroutinesIgnore
suspend fun ignoredSuspendFunction () { } Wenn Sie aus irgendeinem Grund Ihre Kotlin -Erklärungen in Swift weiter verfeinern möchten, können Sie die NativeCoroutinesRefined und NativeCoroutinesRefinedState Annotationen verwenden.
Diese werden das Plugin mitteilen, dass er die erzeugte Eigenschaften/Funktion den Annotation ShouldRefineInSwift hinzufügen soll.
Notiz
Dies erfordert derzeit eine modulweite Opt-In für kotlin.experimental.ExperimentalObjCRefinement .
Sie können beispielsweise Ihre Flow -Eigenschaft in einer AnyPublisher -Eigenschaft verfeinern:
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 )
}
}