KMP-NativeCoroutines

repository·master·Indexed 20 days ago

https://github.com/rickclephas/kmp-nativecoroutines

A library that bridges Kotlin Coroutines (suspend functions and Flows) to Swift in Kotlin Multiplatform (KMP) applications. It provides native Swift cancellation support and restores type-safe generics for flows. It offers multiple Swift implementations including Swift Concurrency (async/await), Combine, and RxSwift, and utilizes a Kotlin plugin and KSP for code generation.

Tokens
4.6K
Snippets
14
Records
23
Agent score
80%

What's inside KMP-NativeCoroutines

  1. What is KMP-NativeCoroutines and why use it?

    master

    KMP-NativeCoroutines is a library designed to bridge Kotlin Coroutines and Swift code in Kotlin Multiplatform (KMP) applications.

    It solves two primary limitations of the default Kotlin/Native interoperability:

    1. Cancellation Support: Standard Kotlin suspend functions are exposed to Swift as functions with completion handlers, which do not support native Swift cancellation. This library enables proper cancellation support.
    2. Generics in Protocols: Because Objective-C does not support generics on protocols, standard Kotlin Flow interfaces lose their generic value types when viewed from Swift. This library restores type safety for flows.

    Note: While Swift 5.5+ allows calling completion-handler-based functions as async functions, this is only syntactic sugar and does not provide true cancellation support. KMP-NativeCoroutines provides the actual mechanism for cancellation.

  2. Use Flows with Swift export

    master

    When swiftExport = true is enabled, Kotlin Flows can be used as Swift AsyncSequences. It is recommended to keep using the asyncSequence(for:) wrapper for now, though it is currently a no-op and may eventually be replaced by asAsyncSequence().

    Important Behavior Change: Upon cancellation, Swift export will throw a CancellationError instead of ending the iteration by returning nil.

    For Combine or RxSwift integration, wrap the flow using the asyncSequence helper inside the publisher creation.

  3. Use suspend functions with Swift export

    master

    When swiftExport = true is enabled, you can use Kotlin suspend functions as Swift async functions. While you can call them directly, it is currently recommended to continue using the asyncFunction(for:) wrapper. Note that asyncFunction(for:) is currently a no-op and may be removed in future versions once Swift export stabilizes.

    For Combine or RxSwift integration, use the closure-based helper functions instead of the original ones.

  4. Use improved property and function names in Swift

    master

    In version 1.0+, the plugin reuses the original property and function names for their native versions. You should remove the Native suffixes from your Swift code calls.

    Example Refactoring:

    Before:

    createPublisher(for: clock.timeNative)
    createFuture(for: randomLettersGenerator.getRandomLettersNative())
    let value = clock.timeNativeValue
    let replayCache = clock.timeNativeReplayCache

    After:

    createPublisher(for: clock.time)
    createFuture(for: randomLettersGenerator.getRandomLetters())
    let value = clock.timeValue
    let replayCache = clock.timeReplayCache

    Note: You can customize or remove the value and replayCache suffixes via configuration (see README).

    // Before v1.0
    createPublisher(for: clock.timeNative)
    createFuture(for: randomLettersGenerator.getRandomLettersNative())
    let value = clock.timeNativeValue
    let replayCache = clock.timeNativeReplayCache
    
    // After v1.0
    createPublisher(for: clock.time)
    createFuture(for: randomLettersGenerator.getRandomLetters())
    let value = clock.timeValue
    let replayCache = clock.timeReplayCache
  5. Configure KSP for KMP-NativeCoroutines v1.0+

    master

    Starting with version 1.0, the plugin uses KSP to generate the required Kotlin code. You must add the KSP plugin to your plugins block in your Kotlin project configuration.

    Note: Ensure you use compatible library versions for both your Kotlin and Swift code.

    plugins {
         id("com.google.devtools.ksp") version "<ksp-version>"
         id("com.rickclephas.kmp.nativecoroutines") version "<version>"
    }
  6. Annotate Kotlin Coroutines for Swift usage

    master

    To expose Kotlin Coroutines to Swift, use the KMP-NativeCoroutines annotations. The plugin automatically generates native wrappers (like AsyncStream, Publisher, or AsyncSequence) based on these annotations.

    • Use @NativeCoroutines for general Flow properties or suspend functions.
    • Use @NativeCoroutinesState specifically for StateFlow properties to ensure both the value and the flow are exposed.

    Generated properties for Flows:

    • For Flow: A native version of the flow (e.g., timeNative) and, if it's a StateFlow, a timeValue property.
    • For SharedFlow: A timeReplayCache property.
    import com.rickclephas.kmp.nativecoroutines.NativeCoroutines
    import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState
    
    class Clock {
        @NativeCoroutines
        val time: StateFlow<Long>
    
        @NativeCoroutinesState
        val state: StateFlow<Long>
    }
    
    class RandomLettersGenerator {
        @NativeCoroutines
        suspend fun getRandomLetters(): String
    }
  7. Install the KMP-NativeCoroutines Kotlin plugin

    master

    To use the library, you must first add the Kotlin plugin to your build.gradle.kts file. You must also opt into the experimental @ObjCName annotation in your Kotlin configuration.

    Important: Always ensure the version of the Kotlin plugin matches the version used in your Swift implementation.

    plugins {
        id("com.rickclephas.kmp.nativecoroutines") version "1.0.5"
    }
    
    kotlin.sourceSets.all {
        languageSettings.optIn("kotlin.experimental.ExperimentalObjCName")
    }
  8. Enable Swift export compatibility mode

    master

    To use KMP-NativeCoroutines alongside the experimental Kotlin Swift export (available in Kotlin 2.2.20+), you must activate the compatibility mode in your build.gradle.kts file. This prevents build failures by cloning original functions and properties, as NativeSuspend and NativeFlow are currently unsupported due to Swift export's limitations with functional return types and generics.

    // build.gradle.kts
    nativeCoroutines {
        swiftExport = true
    }
  9. Install KMP-NativeCoroutines via Swift Package Manager (SPM)

    master

    You can install the Swift components via SPM by adding the repository URL to your Package.swift or by using Xcode's File > Add Packages... menu with the URL: https://github.com/rickclephas/KMP-NativeCoroutines.git.

    Depending on your needs, you can import specific products for Swift Concurrency, Combine, or RxSwift.

    Note: The Swift package version should not include Kotlin version suffixes (e.g., do not use -kotlin-1.6.0). If you only need one implementation, you can use the SPM-specific versions with suffixes like -spm-async, -spm-combine, or -spm-rxswift.

    dependencies: [
        .package(url: "https://github.com/rickclephas/KMP-NativeCoroutines.git", exact: "1.0.5")
    ],
    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")
            ]
        )
    ]
  10. Install KMP-NativeCoroutines via CocoaPods

    master

    Add the desired implementation to your Podfile. Ensure you use the correct tag matching your library version and avoid adding Kotlin version suffixes to the tag.

    Available pods:

    • KMPNativeCoroutinesAsync: Swift Concurrency implementation
    • KMPNativeCoroutinesCombine: Combine implementation
    • KMPNativeCoroutinesRxSwift: RxSwift implementation
    pod 'KMPNativeCoroutinesAsync', git: 'https://github.com/rickclephas/KMP-NativeCoroutines.git', tag: 'v1.0.5'
    pod 'KMPNativeCoroutinesCombine', git: 'https://github.com/rickclephas/KMP-NativeCoroutines.git', tag: 'v1.0.5'
    pod 'KMPNativeCoroutinesRxSwift', git: 'https://github.com/rickclephas/KMP-NativeCoroutines.git', tag: 'v1.0.5'
  11. Customize generated code suffixes in build.gradle.kts

    master

    You can customize the naming conventions of the generated Kotlin properties and functions by configuring the nativeCoroutines block in your build.gradle.kts file. This allows you to change the suffixes used for native coroutine functions, file names, StateFlow value properties, SharedFlow replay caches, and state properties.

    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"
    }