مكتبة لاستخدام Kotlin Coroutines من Swift Code في تطبيقات KMP.
كل من KMP و Kotlin Coroutines مذهلة ، ولكن معا لديهما بعض القيود.
أهم قيود هو دعم الإلغاء.
تتعرض وظائف تعليق Kotlin لـ Swift كوظائف ذات معالج إكمال.
يتيح لك ذلك استخدامها بسهولة من الرمز السريع الخاص بك ، لكنه لا يدعم الإلغاء.
ملحوظة
في حين أن Swift 5.5 يجلب وظائف Async إلى Swift ، فإنها لا تحل هذه المشكلة.
من أجل التشغيل البيني مع OBJC ، يمكن تسمية جميع الوظائف باستخدام معالج الانتهاء مثل وظيفة Async.
هذا يعني أن تبدأ وظائف SWIFT 5.5 الخاصة بك في Kotlin Sopdend مثل وظائف 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:
| تطبيق | سريع | iOS | ماكوس | tvos | Watchos |
|---|---|---|---|---|---|
| غير متزامن | 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.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 أو غير متزامنة أو ناشرين أو ملاحظات.
سيقوم المكون الإضافي بإنشاء الكود اللازم لك بشكل تلقائي! ؟
ما عليك سوى التعليق على إعلانات 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 فإن البرنامج المساعد سيؤدي إلى إنشاء خاصية ذاكرة التخزين المؤقت Replay:
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() }لسوء الحظ ، لا يتم دعم وظائف/خصائص التمديد على بروتوكولات الهدف-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 بعض الوظائف للحصول على وظائف SWIFT ASYNC و 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 s.
بمعنى أن كل اشتراك سيؤدي إلى تجميع Flow أو تنفيذ وظيفة التعليق.
ملحوظة
يجب عليك الاحتفاظ بالإشارة إلى S التي تم Cancellable وإلا سيتم إلغاء المجموعة على الفور.
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 Single Observable .
ملحوظة
هذه الوظائف تخلق مؤجلة Observable و 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 .
سوف تخبر هذه المكون الإضافي بإضافة تعليق توضيحي 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 )
}
}