# KMP-NativeCoroutines gives Swift a coroutine it can actually cancel

> KMP-NativeCoroutines is a library for using Kotlin coroutines from Swift in Kotlin Multiplatform applications, and it exists to solve two specific interop gaps. A suspend function exported to Swift becomes a completion handler, which cannot be cancelled, and the Objective-C bridge drops the generic value type from every Flow interface. The library fixes both by generating native Swift wrappers with a compiler plugin, and it offers three implementations, Async, Combine and RxSwift.

**rickclephas/KMP-NativeCoroutines** — Library to use Kotlin Coroutines from Swift code in KMP apps

- Repository: https://github.com/rickclephas/KMP-NativeCoroutines
- Stars: 1,328 · Forks: 50
- Language: Kotlin
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/rickclephas-kmp-nativecoroutines

## A suspend function loses its cancellation when it crosses into Swift

The library's own explanation of why it exists is precise, and the limitation it names first is cancellation support. Kotlin suspend functions are exposed to Swift as functions with a completion handler, which allows you to use them from your Swift code easily, but it does not support cancellation. So a Swift caller that navigates away from a screen cannot cancel the coroutine on the other side, and the Kotlin code keeps running. The project addresses the obvious objection head on, in a note that says while Swift 5.5 brings async functions to Swift, it does not solve this issue, because for interoperability with Objective-C all functions with a completion handler can be called like an async function, which means that starting with Swift 5.5 your Kotlin suspend functions will look like Swift async functions, but that is just syntactic sugar, so there is still no cancellation support. That is the difference between a language feature and a runtime one, and this library is on the runtime side.

## And the Objective-C bridge drops the generic type

The second limitation is a different kind of loss. Objective-C does not support generics on protocols, so all the Flow interfaces lose their generic value type, which makes them hard to use. A Swift developer looking at a Flow of a particular type sees a Flow with no type parameter, and the type information that the Kotlin side has is simply not there. The library solves both of these limitations, and the two together explain why the project exists, because they are the two things that the standard Kotlin Multiplatform export cannot do and that any real application will hit. The second is easy to underestimate until you try to write a Swift view model against a Flow and discover that the element type is not known, and the first is easy to underestimate until a user backs out of a screen and a request continues anyway.

## Three Swift implementations, and what they cost you

The library lets you choose between a couple of Swift implementations, and the table shows what each one costs you in platform support. The Async implementation needs Swift 5.5 and supports iOS 13.0, macOS 10.15, tvOS 13.0 and watchOS 6.0. The Combine implementation needs Swift 5.0 with the same platform minimums. The RxSwift implementation needs Swift 5.0 and goes back much further, with iOS 9.0, macOS 10.9, tvOS 9 and watchOS 3.0. The choice is therefore a trade between the modern concurrency model and the reach of your application, and for a project that still supports older systems the RxSwift implementation is the one that keeps the deployment target low. The three implementations are separate Swift packages and separate CocoaPods pods, so you can depend on exactly the one you want rather than on all three.

## Installing the Kotlin and Swift halves, with matching versions

The library consists of a Kotlin part and a Swift part that you add to your project, and the Kotlin part is on Maven Central while the Swift part installs through CocoaPods or the Swift Package Manager. The instruction to make sure you always use the same versions for all the libraries is the important one. On the Kotlin side the plugin goes into build.gradle.kts:

```kotlin
plugins {
    id("com.rickclephas.kmp.nativecoroutines") version "1.0.6"
}
```

and you have to opt in to the experimental ObjCName annotation, because the generated code depends on it:

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

On the Swift side, with the Swift Package Manager you add the package with an exact version and then the products you need:

```swift
dependencies: [
    .package(url: "https://github.com/rickclephas/KMP-NativeCoroutines.git", exact: "1.0.6")
],
targets: [
    .target(
        name: "MyTargetName",
        dependencies: [
            .product(name: "KMPNativeCoroutinesAsync", package: "KMP-NativeCoroutines"),
            .product(name: "KMPNativeCoroutinesCombine", package: "KMP-NativeCoroutines"),
            .product(name: "KMPNativeCoroutinesRxSwift", package: "KMP-NativeCoroutines")
        ]
    )
]
```

The CocoaPods route declares one pod per implementation against the same git tag, and two notes apply to both: the version for the Swift package should not contain the Kotlin version suffix, and if you need only one implementation there are SPM specific versions with the suffixes -spm-async, -spm-combine and -spm-rxswift.

## A compiler plugin generates the Swift-facing code

Using your Kotlin Coroutines code from Swift is almost as easy as calling the Kotlin code, and the mechanism is that the plugin generates the necessary code for you. You annotate your coroutine declarations with @NativeCoroutines, or @NativeCoroutinesState, and the plugin produces a native property. The example is a Flow property on a class, and the generated code is a property whose getter calls asNativeFlow() and carries an ObjCName annotation so the name survives into Objective-C:

```kotlin
import com.rickclephas.kmp.nativecoroutines.asNativeFlow
import kotlin.native.ObjCName

@ObjCName(name = "time")
val Clock.timeNative
    get() = time.asNativeFlow()
```

For a StateFlow the plugin also generates a value property, and for a SharedFlow a replay cache property, which is what lets a Swift consumer read the current value or the replay buffer directly rather than only subscribing. The repository layout shows how much machinery is involved, with separate modules for the annotations, the compiler, an embeddable compiler, the Gradle plugin, the core runtime and the IntelliJ plugin, alongside the three Swift implementation directories with their test targets.

## The version matrix is a documentation page in its own right

The compatibility table is long and it is the thing to read before you pin a version. The latest library version uses Kotlin 2.4.20, and the table lists every earlier release with the Kotlin version and the Coroutines version it was built against, going back through 1.0.5 on Kotlin 2.4.10, 1.0.4 on 2.4.0, 1.0.3 on 2.3.21, 1.0.2 on 2.3.20, 1.0.1 on 2.3.10 and 1.0.0 on 2.3.0, and then a long run of ALPHA builds down to 1.0.0-ALPHA-33 on Kotlin 2.0.0 with Coroutines 1.8.1. Two project documents sit alongside it. SWIFT_EXPORT.md covers the current state and the limitations of the newer Swift export path, which the README links with a note for people looking to try it, and MIGRATING_TO_V1.md covers the move to the 1.x line. The release history shows the same pattern, with v1.0.6 on 2026-09-07 and two earlier builds carrying the Kotlin version in the tag, v1.0.5-kotlin-2.4.20-RC and v1.0.5-kotlin-2.4.20-Beta2, which is how a library keeps working across Kotlin releases without holding anybody up.

## The IDE plugin and the sample project

There is a JetBrains plugin, listed on the JetBrains Marketplace, and it is not a convenience. It gives you annotation usage validation, so a @NativeCoroutines on something that cannot be exported is flagged as you type, exposed coroutines warnings for the cases the plugin believes it cannot handle, and quick fixes to add the annotations. The last one is the feature that makes the library usable at scale, because adding an annotation to every suspend function and every Flow by hand is exactly the kind of chore that stops happening after the first week. The sample/ directory is a full multiplatform application with Async, Combine, RxSwift and IntegrationTests subdirectories, shared code, UI, and the platform app targets for iOS, macOS, tvOS and watchOS, including the WatchKit extension, and a SwiftExportHelpers.swift file for the newer export path. The licence is MIT with a LICENSE.txt at the root, and the last push was on 2026-10-04.

## Conclusion

KMP-NativeCoroutines is the right library if your Kotlin Multiplatform code has suspend functions and Flows that the Swift side has to drive, and the plain Objective-C export gives you neither cancellation nor generic types. It is the wrong tool if your shared layer is small enough that the completion handler is fine, because the plugin and the version matrix are a real cost in setup, and it is the wrong tool if the Swift side does not need to cancel anything, since that is the whole problem it solves. Before you adopt it, read the compatibility table, because the library version and the Kotlin version are coupled and the Swift package version must not carry the Kotlin suffix, and check SWIFT_EXPORT.md if you are tempted to use the newer export path, since the project documents its current state and limitations there.

## FAQ

### What problem does KMP-NativeCoroutines solve?

Two. Kotlin suspend functions exported to Swift become functions with a completion handler, which cannot be cancelled, and the Objective-C bridge does not support generics on protocols, so Flow interfaces lose their generic value type. The library generates native Swift wrappers that restore both.

### Which Swift implementations does KMP-NativeCoroutines offer?

Three. Async, which needs Swift 5.5 and supports iOS 13.0, macOS 10.15, tvOS 13.0 and watchOS 6.0, Combine, which needs Swift 5.0 with the same minimums, and RxSwift, which needs Swift 5.0 and reaches back to iOS 9.0, macOS 10.9, tvOS 9 and watchOS 3.0.

### How do I add KMP-NativeCoroutines to a project?

Add the Gradle plugin with id("com.rickclephas.kmp.nativecoroutines") version "1.0.6" and opt in to kotlin.experimental.ExperimentalObjCName in your source sets, then add the Swift package or the CocoaPods pod for the implementation you want. The versions of the Kotlin and Swift halves must match, and the Swift package version should not carry the Kotlin version suffix.

### How does the library generate the Swift-facing API?

A Kotlin compiler plugin reads your declarations annotated with @NativeCoroutines or @NativeCoroutinesState and generates the native properties, including a value property for a StateFlow and a replay cache property for a SharedFlow. The repository contains the compiler, an embeddable compiler, the Gradle plugin, the annotations, the core runtime and an IntelliJ plugin that validates the annotations and offers quick fixes.

## Sources

- [Issues](https://github.com/rickclephas/KMP-NativeCoroutines/issues)
- [License: MIT](https://github.com/rickclephas/KMP-NativeCoroutines/blob/master/LICENSE)
- [README](https://github.com/rickclephas/KMP-NativeCoroutines/blob/master/README.md)
- [Releases](https://github.com/rickclephas/KMP-NativeCoroutines/releases)
- [rickclephas/KMP-NativeCoroutines on GitHub](https://github.com/rickclephas/KMP-NativeCoroutines)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rickclephas-kmp-nativecoroutines
