Skip to content

Closures

Swift closures whose parameter and return types are all exported map directly to Kotlin/Java lambdas. This lets you pass callbacks from Kotlin into Swift and have Swift call them.

Define a Swift method that accepts a closure:

import Swift4j
@jvm
class GreetingService {
func greet(name: String, _ response: (Greeting) -> Void) {
response(Greeting(name: name))
}
}
@jvm
class Greeting {
let name: String
init(name: String) { self.name = name }
func getMessage() -> String { "Hello from Swift, \(name)!" }
}

Both Greeting (the closure parameter type) and GreetingService must be annotated with @jvm. Any type that appears in a closure signature must be exported.

Call it from Kotlin by passing a lambda:

val service = GreetingService()
service.greet("Kotlin") { greeting ->
println(greeting.getMessage())
}

Closures can return values. The return type must be an exported type or a primitive:

@jvm
class Arrays {
static func mapReversed(_ arr: [Int], mapping: (Int) -> Int) -> [Int] {
arr.reversed().map(mapping)
}
}
val result = Arrays.mapReversed(longArrayOf(1, 2, 3)) { it * 2 }
// result: [6, 4, 2]

Closure parameters may be optional. Pass null from Kotlin to omit the callback:

@jvm
struct Task {
func run(completion: (() -> Void)? = nil) {
// ... do work ...
completion?()
}
}
val task = Task()
task.run(completion: null) // no callback
task.run { println("done") }

Closures are called synchronously by default. If the Swift method stores the closure and calls it later (e.g. from a background thread), make sure the Kotlin lambda does not capture JVM-thread-local state that may be unavailable on the calling thread.

For asynchronous patterns, consider using async methods instead — see Async.