Kotlin Coroutines and Swift
Kotlin Multiplatform allows us to share code between different platforms, including iOS where Swift is the primary language. Both Kotlin and Swift have evolved to support modern asynchronous programming patterns - Kotlin with Coroutines and Swift with async/await and structured concurrency. However, their approaches and APIs differ significantly. In this article, we will explore how to make Kotlin Coroutines work seamlessly with Swift's async/await, and how to bridge between these two concurrency models.
Understanding Swift's Async/Await and Structured Concurrency
Swift introduced async/await in Swift 5.5, along with structured concurrency features like Tasks, TaskGroups, and actors. This modern concurrency model shares many similarities with Kotlin Coroutines, but there are important differences:
Similarities:
- Both use cooperative concurrency instead of preemptive threading
- Both support cancellation and structured concurrency
- Both provide async/await syntax for sequential asynchronous code
- Both support concurrent execution of multiple operations
Key Differences:
- Swift uses
Task
for unstructured concurrency, while Kotlin useslaunch
andasync
- Swift has built-in actor isolation, while Kotlin relies on thread-safe data structures (it also supports actors, but this support is limited, rarely used and marked as obsolete)
- Swift's async functions are marked with
async
, Kotlin usessuspend
modifier to mark functions that can suspend the coroutine andlaunch
andasync
to start coroutines
// Swift async function
func fetchUserData(userId: String) async throws -> User {
let response = try await networkService.get("/users/\(userId)")
return try JSONDecoder().decode(User.self, from: response)
}
// Swift Task for unstructured concurrency
Task {
do {
let user = try await fetchUserData(userId: "123")
print("User: \(user.name)")
} catch {
print("Error: \(error)")
}
}
Converting Kotlin Suspending Functions to Swift Async Functions
Kotlin/Native automatically converts suspending functions to Swift async functions when they are exposed to the iOS target:
This becomes available in Swift as:
// Generated Swift interface
class UserRepository : KotlinBase {
func getUser(id: String) async throws -> User
func saveUser(user: User) async throws -> KotlinBoolean
}
// Usage in Swift
let repository = UserRepository()
Task {
do {
let user = try await repository.getUser(id: "123")
let success = try await repository.saveUser(user: user)
print("Save successful: \(success)")
} catch {
print("Error: \(error)")
}
}
Kotlin exceptions are automatically converted to Swift errors. However, you should be careful about the types of exceptions you throw, as not all Kotlin exceptions have direct Swift equivalents:
// Swift usage with error handling
let apiService = ApiService()
Task {
do {
let data = try await apiService.fetchData()
print("Data: \(data)")
} catch {
// Custom exceptions from Kotlin arrive as KotlinException/NSError
print("Error: \(error.localizedDescription)")
}
}
Converting Flow to AsyncSequence
Kotlin Flow can be converted to Swift's AsyncSequence for streaming data. Nowadays there are solid libraries that help with this conversion, such as:
I covered in a separate article how to convert Kotlin Flow to a callback-based API. Basically you can define a wrapper function or class that collects the flow and calls a callback with each emitted value.
Then in Swift, you can create an AsyncSequence:
extension FlowWrapper {
func updates() -> AsyncStream<DataUpdate> {
AsyncStream { continuation in
let job = self.observeUpdates(
onEach: { update in
continuation.yield(update)
},
onError: { error in
continuation.finish(throwing: error)
},
onCompletion: {
continuation.finish()
}
)
continuation.onTermination = { _ in
job.cancel()
}
}
}
}
Here is an example view model wrapper:
Usage in Swift:
let dataStream = viewModel.state.updates()
Task {
do {
for try await update in dataStream {
print("Received update: \(update.message)")
}
} catch {
print("Stream error: \(error)")
}
}
Using Swift Async Functions from Kotlin
When you need to call Swift async functions from Kotlin code, you need to bridge between the two concurrency models. This typically involves creating wrapper functions that convert Swift's async/await to Kotlin's suspending functions.
Creating Suspending Wrappers
You can create Kotlin suspending functions that call Swift async functions using continuation-based approach:
Handling Swift Tasks in Kotlin
When working with Swift Tasks from Kotlin, you need to be careful about cancellation and lifecycle management:

Conclusion
In this article, we explored how to bridge Kotlin Coroutines and Swift's async/await model. We covered how to convert suspending functions to Swift async functions, handle exceptions, and adapt Kotlin Flow to Swift's AsyncSequence. We also looked at how to call Swift async functions from Kotlin code and manage Swift Tasks in a Kotlin-friendly way.