Perpustakaan untuk menggunakan Kotlin coroutine dari SWIFT CODE di aplikasi KMP.
Baik KMP dan Kotlin coroutine luar biasa, tetapi bersama -sama mereka memiliki beberapa keterbatasan.
Keterbatasan yang paling penting adalah dukungan pembatalan.
Fungsi Suspend Kotlin terpapar Swift sebagai fungsi dengan penangan penyelesaian.
Ini memungkinkan Anda untuk dengan mudah menggunakannya dari kode Swift Anda, tetapi tidak mendukung pembatalan.
Catatan
Sementara Swift 5.5 membawa fungsi async ke Swift, itu tidak menyelesaikan masalah ini.
Untuk interoperabilitas dengan OBJC semua fungsi dengan penangan penyelesaian dapat disebut seperti fungsi async.
Ini berarti dimulai dengan Swift 5.5 fungsi penangguhan Kotlin Anda akan terlihat seperti fungsi async Swift.
Tapi itu hanya gula sintaksis, jadi masih belum ada dukungan pembatalan.
Selain dukungan pembatalan, OBJC tidak mendukung generik pada protokol.
Jadi semua antarmuka Flow kehilangan jenis nilai generik yang membuatnya sulit digunakan.
Perpustakaan ini memecahkan kedua batasan ini ?.
Versi terbaru dari perpustakaan menggunakan Kotlin versi 2.1.0 .
Versi kompatibilitas untuk versi Kotlin yang lebih tua dan/atau pratinjau juga tersedia:
| Versi | Sufiks versi | Kotlin | Ksp | Coroutines |
|---|---|---|---|---|
| terbaru | tidak ada akhiran | 2.1.0 | 1.0.29 | 1.9.0 |
| 1.0.0-alpha-37 | tidak ada akhiran | 2.0.21 | 1.0.25 | 1.9.0 |
| 1.0.0-alpha-36 | tidak ada akhiran | 2.0.20 | 1.0.25 | 1.9.0 |
| 1.0.0-alpha-35 | tidak ada akhiran | 2.0.20 | 1.0.24 | 1.8.1 |
| 1.0.0-alpha-34 | tidak ada akhiran | 2.0.10 | 1.0.24 | 1.8.1 |
| 1.0.0-alpha-33 | tidak ada akhiran | 2.0.0 | 1.0.24 | 1.8.1 |
| 1.0.0-alpha-30 | tidak ada akhiran | 1.9.24 | 1.0.20 | 1.8.1 |
| 1.0.0-Alpha-28 | tidak ada akhiran | 1.9.23 | 1.0.20 | 1.8.0 |
| 1.0.0-Alpha-25 | tidak ada akhiran | 1.9.22 | 1.0.17 | 1.8.0 |
| 1.0.0-Alpha-23 | tidak ada akhiran | 1.9.21 | 1.0.16 | 1.7.3 |
| 1.0.0-Alpha-21 | tidak ada akhiran | 1.9.20 | 1.0.14 | 1.7.3 |
| 1.0.0-Alpha-18 | tidak ada akhiran | 1.9.10 | 1.0.13 | 1.7.3 |
| 1.0.0-Alpha-17 | tidak ada akhiran | 1.9.0 | 1.0.12 | 1.7.3 |
| 1.0.0-Alpha-12 | tidak ada akhiran | 1.8.22 | 1.0.11 | 1.7.2 |
| 1.0.0-alpha-10 | tidak ada akhiran | 1.8.21 | 1.0.11 | 1.7.1 |
| 1.0.0-alpha-7 | tidak ada akhiran | 1.8.20 | 1.0.10 | 1.6.4 |
Anda dapat memilih dari beberapa implementasi Swift.
Tergantung pada implementasi yang dapat Anda dukung serendah iOS 9, MacOS 10.9, TVOS 9 dan WatchOS 3:
| Pelaksanaan | Cepat | iOS | MacOS | TVOS | Watchos |
|---|---|---|---|---|---|
| Async | 5.5 | 13.0 | 10.15 | 13.0 | 6.0 |
| Menggabungkan | 5.0 | 13.0 | 10.15 | 13.0 | 6.0 |
| Rxswift | 5.0 | 9.0 | 10.9 | 9.0 | 3.0 |
Perpustakaan terdiri dari bagian Kotlin dan Swift yang perlu Anda tambahkan ke proyek Anda.
Bagian Kotlin tersedia di Maven Central dan bagian Swift dapat dipasang melalui Cocoapods atau Swift Package Manager.
Pastikan untuk selalu menggunakan versi yang sama untuk semua perpustakaan!
Untuk Kotlin cukup tambahkan plugin ke build.gradle.kts Anda:
plugins {
id( " com.google.devtools.ksp " ) version " 2.1.0-1.0.29 "
id( " com.rickclephas.kmp.nativecoroutines " ) version " 1.0.0-ALPHA-38 "
} Dan pastikan untuk memilih anotasi @ObjCName eksperimental:
kotlin.sourceSets.all {
languageSettings.optIn( " kotlin.experimental.ExperimentalObjCName " )
} Implementasi Swift tersedia melalui Swift Package Manager.
Cukup tambahkan ke file Package.swift Anda:
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 " )
]
)
] Atau tambahkan di Xcode dengan pergi ke File > Add Packages... dan menyediakan URL: https://github.com/rickclephas/KMP-NativeCoroutines.git .
Catatan
Versi untuk paket Swift tidak boleh berisi sufiks versi Kotlin (misalnya -new-mm atau -kotlin-1.6.0 ).
Catatan
Jika Anda hanya membutuhkan satu implementasi, Anda juga dapat menggunakan versi spesifik SPM dengan sufiks -spm-async , -spm-combine dan -spm-rxswift .
Jika Anda menggunakan cocoapod, tambahkan satu atau lebih perpustakaan berikut ke Podfile Anda:
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 Catatan
Versi untuk cocoapods tidak boleh berisi sufiks versi Kotlin (misalnya -new-mm atau -kotlin-1.6.0 ).
Instal plugin IDE dari pasar JetBrains untuk mendapatkan:
Menggunakan kode Kotlin Coroutine Anda dari Swift hampir semudah memanggil kode Kotlin.
Cukup gunakan fungsi pembungkus di Swift untuk mendapatkan fungsi async, asyncstreams, publishers atau Observable.
Plugin akan secara otomatis menghasilkan kode yang diperlukan untuk Anda! ?
Cukup anotasi deklarasi coroutines Anda dengan @NativeCoroutines (atau @NativeCoroutinesState ).
Properti/fungsi Flow Anda mendapatkan versi asli:
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
}Plugin ini akan menghasilkan properti asli ini untuk Anda:
import com.rickclephas.kmp.nativecoroutines.asNativeFlow
import kotlin.native.ObjCName
@ObjCName(name = " time " )
val Clock .timeNative
get() = time.asNativeFlow() Untuk StateFlow yang ditentukan di atas plugin juga akan menghasilkan properti nilai ini:
val Clock .timeValue
get() = time.value Dalam hal SharedFlow plugin akan menghasilkan properti cache replay:
val Clock .timeReplayCache
get() = time.replayCache Menggunakan properti StateFlow untuk melacak status (seperti dalam model tampilan)?
Gunakan anotasi @NativeCoroutinesState sebagai gantinya:
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
}Plugin ini akan menghasilkan properti asli ini untuk Anda:
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()Plugin ini juga menghasilkan versi asli untuk fungsi penangguhan Anda yang beranotasi:
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
}
}Plugin akan menghasilkan fungsi asli ini untuk Anda:
import com.rickclephas.kmp.nativecoroutines.nativeSuspend
import kotlin.native.ObjCName
@ObjCName(name = " getRandomLetters " )
fun RandomLettersGenerator. getRandomLettersNative () =
nativeSuspend { getRandomLetters() }Sayangnya fungsi/properti ekstensi tidak didukung pada protokol Objective-C.
Namun batasan ini dapat "diatasi" dengan beberapa sihir cepat.
Dengan asumsi RandomLettersGenerator adalah interface alih -alih class yang dapat kita lakukan sebagai berikut:
import KMPNativeCoroutinesCore
extension RandomLettersGenerator {
func getRandomLetters ( ) -> NativeSuspend < String , Error , KotlinUnit > {
RandomLettersGeneratorNativeKt . getRandomLetters ( self )
}
} Ketika fungsi penangguhan dan/atau deklarasi Flow terpapar OBJC/Swift, kompiler dan plugin IDE akan menghasilkan peringatan, mengingatkan Anda untuk menambahkan salah satu anotasi KMP-nativecoroutine.
Anda dapat menyesuaikan keparahan cek ini di file build.gradle.kts Anda:
nativeCoroutines {
exposedSeverity = ExposedSeverity . ERROR
}Atau, jika Anda tidak tertarik dengan cek ini, nonaktifkan mereka:
nativeCoroutines {
exposedSeverity = ExposedSeverity . NONE
} Implementasi async menyediakan beberapa fungsi untuk mendapatkan fungsi swift async dan AsyncSequence s.
Gunakan asyncFunction(for:) fungsi untuk mendapatkan fungsi async yang bisa ditunggu:
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 ( ) Atau jika Anda tidak suka DO-catch ini, Anda dapat menggunakan asyncResult(for:)
import KMPNativeCoroutinesAsync
let result = await asyncResult ( for : randomLettersGenerator . getRandomLetters ( ) )
if case let . success ( letters ) = result {
print ( " Got random letters: ( letters ) " )
} Untuk fungsi pengembalian Unit ada juga asyncError(for:) function:
import KMPNativeCoroutinesAsync
if let error = await asyncError ( for : integrationTests . returnUnit ( ) ) {
print ( " Failed with error: ( error ) " )
} Untuk Flow S ada asyncSequence(for:) fungsi untuk mendapatkan 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 ( ) Implementasi gabungan menyediakan beberapa fungsi untuk mendapatkan AnyPublisher untuk kode coroutine Anda.
Catatan
Fungsi -fungsi ini membuat AnyPublisher yang ditangguhkan.
Artinya setiap langganan akan memicu pengumpulan Flow atau pelaksanaan fungsi penangguhan.
Catatan
Anda harus menyimpan referensi ke yang dapat Cancellable S jika tidak, koleksi akan segera dibatalkan.
Untuk Flow Anda s Gunakan createPublisher(for:) Function:
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 ( ) Anda juga dapat menggunakan createPublisher(for:) Fungsi untuk fungsi penangguhan yang mengembalikan Flow :
let publisher = createPublisher ( for : randomLettersGenerator . getRandomLettersFlow ( ) ) Untuk fungsi penangguhan Anda harus menggunakan 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 ( ) Implementasi RXSWIFT menyediakan beberapa fungsi untuk mendapatkan yang Observable atau Single untuk kode coroutine Anda.
Catatan
Fungsi -fungsi ini membuat S yang dapat Observable dan s Single .
Artinya setiap langganan akan memicu pengumpulan Flow atau pelaksanaan fungsi penangguhan.
Untuk Flow Anda s Gunakan createObservable(for:) function:
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 ( ) Anda juga dapat menggunakan createObservable(for:) Fungsi untuk fungsi penangguhan yang mengembalikan Flow :
let observable = createObservable ( for : randomLettersGenerator . getRandomLettersFlow ( ) ) Untuk fungsi penangguhan Anda harus menggunakan 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 ( ) Ada sejumlah cara Anda dapat menyesuaikan kode Kotlin yang dihasilkan.
Tidak suka penamaan properti/fungsi yang dihasilkan?
Tentukan sufiks khusus Anda sendiri di file build.gradle.kts Anda:
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 "
} Untuk kontrol lebih lanjut, Anda dapat memberikan CoroutineScope kustom dengan anotasi NativeCoroutineScope :
import com.rickclephas.kmp.nativecoroutines.NativeCoroutineScope
class Clock {
@NativeCoroutineScope
internal val coroutineScope = CoroutineScope (job + Dispatchers . Default )
}Catatan
Lingkup coroutine kustom Anda harus internal atau public .
Jika Anda tidak memberikan CoroutineScope ruang lingkup default akan digunakan yang didefinisikan sebagai:
internal val defaultCoroutineScope = CoroutineScope ( SupervisorJob () + Dispatchers . Default )Catatan
KMP-NativeCoroutines memiliki dukungan bawaan untuk KMP-ObservableViewModel.
Coroutine di dalam ViewModel Anda akan (secara default) menggunakan CoroutineScope dari ViewModelScope .
Gunakan anotasi NativeCoroutinesIgnore untuk memberi tahu plugin untuk mengabaikan properti atau fungsi:
import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesIgnore
@NativeCoroutinesIgnore
val ignoredFlowProperty : Flow < Int >
@NativeCoroutinesIgnore
suspend fun ignoredSuspendFunction () { } Jika karena alasan tertentu Anda ingin lebih menyempurnakan deklarasi Kotlin Anda di Swift, Anda dapat menggunakan anotasi NativeCoroutinesRefined dan NativeCoroutinesRefinedState .
Ini akan memberitahu plugin untuk menambahkan anotasi ShouldRefineInSwift anotasi ke properti/fungsi yang dihasilkan.
Catatan
Ini saat ini membutuhkan opt-in modul-selebar ke kotlin.experimental.ExperimentalObjCRefinement .
Anda dapat misalnya memperbaiki properti Flow Anda ke properti 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 )
}
}