Skip to content

Classes

Swift classes are reference types. Each exported class instance corresponds to a single Swift heap object whose lifetime is managed by JNI reference counting on the JVM side.

Apply @jvm to the class declaration:

import Swift4j
@jvm
class Counter {
private var value = 0
func increment() { value += 1 }
func decrement() { value -= 1 }
func getValue() -> Int { value }
}

The generated Kotlin class has the same name and the same public members:

System.loadLibrary("MyLibrary")
val counter = Counter()
counter.increment()
counter.increment()
println(counter.getValue()) // 2

All non-private init methods are exported. Parameters use the same type mapping as methods.

@jvm
class Connection {
init(host: String, port: Int) { ... }
init(url: String) { ... }
}
val conn = Connection(host: "localhost", port: 5432)

Note: Swift argument labels become named parameters in Kotlin. The first unlabeled parameter (_) becomes a positional parameter.

Stored properties and computed properties are exported as getters and setters:

@jvm
class User {
var name: String
let id: Int
var displayName: String { "\(name) (\(id))" }
private(set) var score: Int = 0
init(id: Int, name: String) {
self.id = id
self.name = name
}
}
  • var properties with both getter and setter → read/write property in Kotlin
  • let properties → read-only (getter only)
  • Computed { get } properties → read-only
  • private(set) properties → read-only from Kotlin

Static members are exported as static methods on the generated class:

@jvm
class MathUtils {
static func add(_ a: Int, _ b: Int) -> Int { a + b }
static let pi = 3.14159
}
val sum = MathUtils.add(2, 3)
val pi = MathUtils.pi

A Swift object stays alive as long as the corresponding Kotlin/Java proxy object is reachable on the JVM heap. When the JVM garbage-collects the proxy, it calls the native deinit callback, which releases the ARC retain count and allows the Swift object to be freed.

deinit runs at GC time, not at scope exit. If your Swift class holds resources that must be released promptly (file handles, network connections), expose an explicit close() or dispose() method and call it from Kotlin before dropping the reference.

@jvm emits a compiler warning for any member it cannot export — for example, a method whose parameter type is not an exported type or a built-in JNI-compatible type. Use @nonjvm to mark such members as intentionally excluded and suppress the warning:

@jvm
class Parser {
func parse(input: String) -> [Token] { ... } // exported
@nonjvm
func parseInternal(buffer: UnsafeBufferPointer<UInt8>) { ... } // excluded, no warning
}

@nonjvm has no runtime effect — it is a compile-time marker only.

Nested classes can be exported if the enclosing class is also exported. On Swift 6, apply @jvm to both:

@jvm
class Parent {
@jvm
class Child {
func hello() -> String { "Hello from Child" }
}
}
val child = Parent.Child()
println(child.hello())