KMPNativeCoroutinesRxSwift 1.0.0-ALPHA-34

KMPNativeCoroutinesRxSwift 1.0.0-ALPHA-34

Rick Clephas 维护。



 
依赖
KMPNativeCoroutinesCore= 1.0.0-ALPHA-34
RxSwift~> 6.0
 

  • 作者:
  • Rick Clephas

KMP-NativeCoroutines

一个库,可以在 KMP 应用中使用 Kotlin 协程调用 Swift 代码。

想要从 0.x 版本升级?查看迁移步骤

为什么有这个库?

KMP 和 Kotlin 协程都很棒,但它们有一些局限性。

最重要的限制是取消支持。
Kotlin 挂起函数作为有完成处理器的函数暴露给 Swift。
这使得您可以从 Swift 代码中轻松使用它们,但不支持取消。

注意:尽管 Swift 5.5 为 Swift 带来了异步函数,但它并没有解决这个问题。
为了与 ObjC 兼容,所有带有完成处理器的函数都可以像异步函数一样调用。
这意味着从 Swift 5.5 开始,您的 Kotlin 挂起函数将看起来像 Swift 异步函数。
但这只是一个语法糖,所以仍然没有取消支持。

除了取消支持外,ObjC 还不支持协议上的泛型。
因此,所有 Flow 接口都失去了它们的泛型值类型,这使得它们难以使用。

这个库解决这两个限制😄.

兼容性

库的最新版本使用 Kotlin 版本 1.9.0
还提供了适用于旧版本和/或预览版 Kotlin 的兼容性版本

版本 版本后缀 Kotlin KSP 协程
最新版 无后缀 1.9.0 1.0.12 1.7.3
1.0.0-ALPHA-13 无后缀 1.9.0 1.0.11 1.7.2
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-8 无后缀 1.8.21 1.0.11 1.6.4
1.0.0-ALPHA-7 无后缀 1.8.20 1.0.10 1.6.4
1.0.0-ALPHA-5 无后缀 1.8.10 1.0.9 1.6.4
1.0.0-ALPHA-4 无后缀 1.8.0 1.0.8 1.6.4

您可以选择几种 Swift 实现。
根据实现方式,您可以支持最低至 iOS 9、macOS 10.9、tvOS 9 和 watchOS 3

实现 Swift iOS macOS tvOS watchOS
异步 5.5 13.0 10.15 13.0 6.0
Combine 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 获得,Swift 部分可通过 CocoaPods 或 Swift 包管理器安装。

请确保始终为所有库使用相同的版本!

Kotlin

对于 Kotlin,只需将插件添加到您的 build.gradle.kts

plugins {
    id("com.google.devtools.ksp") version "1.9.0-1.0.12"
    id("com.rickclephas.kmp.nativecoroutines") version "1.0.0-ALPHA-14"
}

并确保选择实验性的 @ObjCName 注解

kotlin.sourceSets.all {
    languageSettings.optIn("kotlin.experimental.ExperimentalObjCName")
}

Swift (Swift Package Manager)

Swift 实现可以通过 Swift 包管理器获取。
只需将其添加到您的 Package.swift 文件中

dependencies: [
    .package(url: "https://github.com/rickclephas/KMP-NativeCoroutines.git", from: "1.0.0-ALPHA-14")
]

或者在 Xcode 中通过转到 文件 > 添加包...并提供 URL: https://github.com/rickclephas/KMP-NativeCoroutines.git 来添加。

注意:Swift 包的版本不应包含 Kotlin 版本后缀(例如 -new-mm-kotlin-1.6.0)。

注意:如果您只需要单个实现,您还可以使用 SPM 特定的版本,后缀为 -spm-async-spm-combine-spm-rxswift

Swift (CocoaPods)

如果您使用 CocoaPods,请将以下库中的一个或多个添加到您的 Podfile

pod 'KMPNativeCoroutinesAsync', '1.0.0-ALPHA-14'    # Swift Concurrency implementation
pod 'KMPNativeCoroutinesCombine', '1.0.0-ALPHA-14'  # Combine implementation
pod 'KMPNativeCoroutinesRxSwift', '1.0.0-ALPHA-14'  # RxSwift implementation

注意:CocoaPods 的版本不应包含 Kotlin 版本后缀(例如 -new-mm-kotlin-1.6.0)。

使用说明

从 Swift 中使用 Kotlin 协程代码几乎与调用 Kotlin 代码一样简单。
只需使用 Swift 中的包装函数来获取异步函数、AsyncStreams、发布者或可观察者。

Kotlin

插件会自动为您生成必要的代码!🔮
只需使用 @NativeCoroutines(或 @NativeCoroutinesState)注解您的协程声明。

流程

您的流程属性/函数获得了一个本地版本

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协议上不受支持。

然而,这个限制可以通过一些Swift魔法来“克服”。
假设RandomLettersGenerator是一个接口而不是一个,我们可以做以下操作

import KMPNativeCoroutinesCore

extension RandomLettersGenerator {
    func getRandomLetters() -> NativeSuspend<String, Error, KotlinUnit> {
        RandomLettersGeneratorNativeKt.getRandomLetters(self)
    }
}

Swift 并发

Async 实现提供了一些获取异步 Swift 函数和 AsyncSequence 的函数。

Async 函数

使用 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()

或者如果您不喜欢这些 do-catches 结构,可以使用 asyncResult(for:) 函数

import KMPNativeCoroutinesAsync

let result = await asyncResult(for: randomLettersGenerator.getRandomLetters())
if case let .success(letters) = result {
    print("Got random letters: \(letters)")
}

AsyncSequence

对于 Flow,有 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()

Combine

Combine 实现提供了一些函数,用于为您的协程代码获取 AnyPublisher

注意:这些函数创建延迟的 AnyPublisher
这意味着每个订阅都会触发 Flow 的收集或挂起函数的执行。

发布者

为您的 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()

您也可以为返回 Flow 的挂起函数使用 createPublisher(for:) 函数

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

RxSwift 的实现提供了一些函数,用于获取您的协程代码的 ObservableSingle

注意:这些函数创建延迟的 ObservableSingle
这意味着每个订阅都会触发 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()

您也可以为返回 Flow 的挂起函数使用 createObservable(for:) 函数

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 注解提供一个自定义的 CoroutineScope

import com.rickclephas.kmp.nativecoroutines.NativeCoroutineScope

class Clock {
    @NativeCoroutineScope
    internal val coroutineScope = CoroutineScope(job + Dispatchers.Default)
}

注意:您的自定义协程作用域必须是 internalpublic

如果未提供 CoroutineScope,将使用默认的作用域,其定义如下:

internal val defaultCoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

注意:KMP-NativeCoroutines 具有内置对KMM-ViewModel 的支持。
在您的 KMMViewModel 内的协程(默认情况下)将使用来自 ViewModelScopeCoroutineScope

忽略声明

使用 NativeCoroutinesIgnore 注解来告诉插件忽略一个属性或函数。

import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesIgnore

@NativeCoroutinesIgnore
val ignoredFlowProperty: Flow<Int>

@NativeCoroutinesIgnore
suspend fun ignoredSuspendFunction() { }

在 Swift 中细化声明

如果您想进一步在 Swift 中细化您的 Kotlin 声明,可以使用 NativeCoroutinesRefinedNativeCoroutinesRefinedState 注解。
这将指示插件将 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)
    }
}