ห้องสมุดที่ใช้ Kotlin coroutines จาก Swift Code ในแอพ KMP
ทั้ง KMP และ Kotlin coroutines นั้นน่าทึ่ง แต่ด้วยกันพวกเขามีข้อ จำกัด บางอย่าง
ข้อ จำกัด ที่สำคัญที่สุดคือการสนับสนุนการยกเลิก
ฟังก์ชั่น Kotlin Suspend จะสัมผัสกับ Swift เป็นฟังก์ชั่นที่มีตัวจัดการความสมบูรณ์
สิ่งนี้ช่วยให้คุณสามารถใช้งานได้อย่างง่ายดายจากรหัส SWIFT ของคุณ แต่ไม่รองรับการยกเลิก
บันทึก
ในขณะที่ Swift 5.5 นำฟังก์ชั่น async มาสู่ Swift แต่ก็ไม่ได้แก้ปัญหานี้
สำหรับการทำงานร่วมกันกับ OBJC ฟังก์ชั่นทั้งหมดที่มีตัวจัดการความสมบูรณ์สามารถเรียกได้ว่าเป็นฟังก์ชั่น async
ซึ่งหมายความว่าการเริ่มต้นด้วย Swift 5.5 ฟังก์ชั่นการระงับ Kotlin ของคุณจะดูเหมือนฟังก์ชั่น Swift Async
แต่นั่นเป็นเพียงน้ำตาลวากยสัมพันธ์ดังนั้นยังไม่มีการสนับสนุนการยกเลิก
นอกเหนือจากการสนับสนุนการยกเลิก OBJC ไม่สนับสนุนยาสามัญในโปรโตคอล
ดังนั้นอินเทอร์เฟซ Flow ทั้งหมดจะสูญเสียประเภทค่าทั่วไปซึ่งทำให้ใช้งานยาก
ห้องสมุดนี้แก้ข้อ จำกัด ทั้งสองนี้ได้หรือไม่
ไลบรารีเวอร์ชันล่าสุดใช้ Kotlin เวอร์ชัน 2.1.0
รุ่นที่เข้ากันได้สำหรับรุ่นเก่าและ/หรือดูตัวอย่าง Kotlin ยังมีอยู่เช่นกัน:
| รุ่น | คำต่อท้ายเวอร์ชัน | 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 |
คุณสามารถเลือกการใช้งาน Swift สองสามอย่าง
ขึ้นอยู่กับการใช้งานที่คุณสามารถรองรับได้ต่ำที่สุดเท่า iOS 9, MacOS 10.9, TVOS 9 และ WatchOS 3:
| การดำเนินการ | ฉับพลัน | iOS | แม็กอส | tvos | นาฬิกานาฬิกา |
|---|---|---|---|---|---|
| async | 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
ตรวจสอบให้แน่ใจว่าใช้เวอร์ชันเดียวกันสำหรับไลบรารีทั้งหมดเสมอ!
สำหรับ 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 Manager
เพียงเพิ่มลงในไฟล์ Package.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 " )
]
)
] หรือเพิ่มใน XCode โดยไปที่ File > Add Packages... และให้ URL: https://github.com/rickclephas/KMP-NativeCoroutines.git
บันทึก
เวอร์ชันสำหรับแพ็คเกจ SWIFT ไม่ควรมีคำต่อท้ายรุ่น KOTLIN (เช่น -new-mm หรือ -kotlin-1.6.0 )
บันทึก
หากคุณต้องการการใช้งานเพียงครั้งเดียวเท่านั้นคุณยังสามารถใช้เวอร์ชันเฉพาะ SPM กับคำต่อท้าย -spm-async , -spm-combine และ -spm-rxswift
หากคุณใช้ 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 )
ติดตั้งปลั๊กอิน IDE จากตลาด Jetbrains เพื่อรับ:
การใช้รหัส Kotlin Coroutines ของคุณจาก Swift นั้นเกือบจะง่ายพอ ๆ กับการโทรรหัส Kotlin
เพียงใช้ฟังก์ชั่น wrapper ใน Swift เพื่อรับฟังก์ชั่น async, asyncstreams, ผู้เผยแพร่หรือสิ่งที่สังเกตได้
ปลั๊กอินจะสร้างรหัสที่จำเป็นสำหรับคุณโดยอัตโนมัติ! -
เพียงแค่ใส่คำอธิบายประกอบการประกาศ coroutines ของคุณด้วย @NativeCoroutines (หรือ @NativeCoroutinesState )
คุณสมบัติ 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
} การใช้งาน Async มีฟังก์ชั่นบางอย่างเพื่อให้ได้ฟังก์ชั่น Async Swift และ AsyncSequence s
ใช้ asyncFunction(for:) ฟังก์ชั่นเพื่อรับฟังก์ชั่น async ที่สามารถรอได้:
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 ( ) หรือถ้าคุณไม่ชอบสิ่งเหล่านี้คุณสามารถใช้ 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 ( ) การใช้งานแบบรวมมีฟังก์ชั่นสองสามอย่างเพื่อรับ AnyPublisher สำหรับรหัส Coroutines ของคุณ
บันทึก
ฟังก์ชั่นเหล่านี้สร้าง 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 มีฟังก์ชั่นสองอย่างเพื่อรับรหัส Observable หรือ Single สำหรับรหัส Coroutines ของคุณ
บันทึก
ฟังก์ชั่นเหล่านี้สร้าง 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 "
} สำหรับการควบคุมเพิ่มเติมคุณสามารถให้ CoroutineScope ที่กำหนดเองด้วยคำอธิบายประกอบ NativeCoroutineScope :
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
Coroutines ภายใน ViewModel ของคุณจะใช้ CoroutineScope จาก ViewModelScope
ใช้คำอธิบายประกอบ NativeCoroutinesIgnore เพื่อบอกปลั๊กอินให้ละเว้นคุณสมบัติหรือฟังก์ชั่น:
import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesIgnore
@NativeCoroutinesIgnore
val ignoredFlowProperty : Flow < Int >
@NativeCoroutinesIgnore
suspend fun ignoredSuspendFunction () { } หากด้วยเหตุผลบางอย่างคุณต้องการปรับแต่งการประกาศ Kotlin ของคุณใน Swift ต่อไปคุณสามารถใช้ NativeCoroutinesRefined และ NativeCoroutinesRefinedState Annotations
สิ่งเหล่านี้จะบอกให้ปลั๊กอินเพิ่มคำอธิบายประกอบ 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 )
}
}