I wan't to abstract two classes from their native implementation in my Kotlin Multiplatform project.
The two classes represent a Position
and a Region
but I also want to be able to transform them into their native objective-c implementation, so into CLLocationCoordinate2D
and MKCoordinateRegion
.
To achieve this I used the expect
actual
pattern but added the two functions toCoreLocation
and toCoreRegion
which should return me their native counterparts.
These functions work fine in the Kotlin code but the generated objective-c code does not return the type of CLLocationCoordinate2D
or MKCoordinateRegion
but just KotlinCValue<AnyObject>
.
This is my commonMain
code:
expect class Position(
latitude: Double,
longitude: Double
)
expect class Region(
center: Position,
latitudinalMeters: Double,
longitudinalMeters: Double
)
This is my actual implementation inside iosMain
:
import platform.MapKit.MKCoordinateRegionMakeWithDistance
import platform.CoreLocation.CLLocationCoordinate2DMake
actual class Position actual constructor(
val latitude: Double,
val longitude: Double
) {
fun toCoreLocation() = CLLocationCoordinate2DMake(
latitude, longitude
)
}
actual class Region actual constructor(
val center: Position,
val latitudinalMeters: Double,
val longitudinalMeters: Double
) {
fun toCoreRegion() = MKCoordinateRegionMakeWithDistance(
center.toCoreLocation(), latitudinalMeters, longitudinalMeters
)
}
And this is the compiled objective-c code:
public class Position : KotlinBase {
public init(latitude: Double, longitude: Double)
open func toCoreLocation() -> KotlinCValue<AnyObject>
open var latitude: Double { get }
open var longitude: Double { get }
}
public class Region : KotlinBase {
public init(center: Position, latitudinalMeters: Double, longitudinalMeters: Double)
open func toCoreRegion() -> KotlinCValue<AnyObject>
open var center: Position { get }
open var latitudinalMeters: Double { get }
open var longitudinalMeters: Double { get }
}
Does anybody know how it's possible to return the native type from the compiled objective-c functions?
Do I maybe need to add .def
file into the nativeInterop/cinterop
directory?
You need to ensure that the value of your object is in a fixed memory position. If you want to to access it in Swift, you need to unpack it first.
To do so, you have two options:
a) You can create a struct in Swift and copy the cValue
to the pointer of the struct via place
in kotlin.
// in Kotlin, Test.kt
fun place(
loc: CValue<CLLocationCoordinate2D>,
ptr: CPointer<CLLocationCoordinate2D>,
) {
// copy the cValue into the pointer address
loc.place(ptr)
}
// in Swift
func unrwap(_ cValue:KotlinCValue<AnyObject>) -> CLLocationCoordinate2D {
var ptr = CLLocationCoordinate2D()
TestKt.place(cValue:cValue, ptr: &ptr)
return ptr // the coordinates are now populated
}
b) You can load the MutableUnsafeRawPointer
while the cValue
is in a memScoped
block. For that you need to write a helper in Kotlin, which takes a callback when the pointer is accessible in the memory. This raw pointer can be instantiated using load.
// in Kotlin
fun interface PtrHandler<T : CStructVar> {
fun handle(ptr: CPointer<T>): Unit
}
object Bridge {
fun <T : CStructVar> load(cValue: CValue<T>, handler: PtrHandler<T>) {
memScoped {
//2: the ptr of cValue is fixed here
handler.handle(cValue.ptr)
}
}
}
// in Swift
private class SwiftPtrHandler<T>:PtrHandler {
var loaded:T {
return _loaded!
}
private var _loaded:T? = nil
//3: receives the raw pointer from the Bridge object
func handle(ptr:UnsafeMutableRawPointer) {
let t = ptr.load(as: T.self)
// 4: populate for later usage
_loaded = t
}
}
func unwrap<T>(_ cValue:KotlinCValue<AnyObject>) -> T {
let handler = SwiftPtrHandler<T>()
//1: call load
Bridge().load(cValue: cValue, handler: handler)
return handler.loaded
}
// usage
let loc:CLLocationCoordinate2D = unwrap(cValue)