Does anyone know how to convert a `KotlinCValue&lt...
# kotlin-native
d
Does anyone know how to convert a
KotlinCValue<AnyObject>
from a KMM library to the actual CValue type when using it in Swift? (I'll turn this into a thread; the question setup / background is a little complicated.)
Background: Shared Library Setup I have a KMM project with a shared library interacting with Android and iOS mapping frameworks. In the shared library, I have a simple
Location
Kotlin data class abstraction that looks like this:
Copy code
// commonMain / Location.kt
data class Location(val longitude: Double, val latitude: Double)
I also have an iOS extension of
Location
to help interact with `platform.CoreLocation`:
Copy code
// iosMain / LocationExtensions.kt
fun Location.asCoordinate(): CValue<CLLocationCoordinate2D> = CLLocationCoordinate2DMake(latitude, longitude)
This works pretty well in the shared library. Any time the platform
CoreLocation
or
MapKit
needs a
CLLocationCoordinate2D
, the
CValue<CLLocationCoordinate2D>
is the right type to use in Kotlin. I've paired this simple data class with a simple abstracton for map annotations:
Copy code
// commonMain / MapMarker.kt
expect class MapMarker(location: Location)
The
MapMarker
is an
expect
/
actual
so that I can feed these obejcts directly to Apple's
MapKit
. The iOS
actual
for
MapMarker
looks somewhat like this (trimmed for brevity):
Copy code
actual class MapMarker actual constructor(
    private val location: Location
) : NSObject(), MKAnnotationProtocol {
    override fun coordinate(): CValue<CLLocationCoordinate2D> {
        return location.asCoordinate()
    }
}
Usage in Swift On the Swift side, I'm able to pair
MapMarker
directly and easily without issue with
MKMapView
, like this:
Copy code
// Simple data setup for illustration purposes,
// this returns an array of `MapMarker` in the shared
// library that we're able to treat as an array of
// `[MKAnnotation]` from within the iosApp.
var annotations: [MKAnnotation] = MockData().mockAnnotations

// It's straightforward to display the annotations
let mapView = MKMapView()
let pointAnnotationViews = annotations.map {
	let pointAnnotation = MKPointAnnotation()
	// Note: MKAnnotation `coordinate` delegates to our
	// shared `Location` `asCoordinate` helper, which
	// returns a `CLLocationCoordinate2D`, as expected.
    pointAnnotation.coordinate = $0.coordinate
    return pointAnnotation
}
view.addAnnotations(pointAnnotationViews)
The problem I'm having is that the
CValue<CLLocationCoordinate2D>
Kotlin return type is treated as a
CLLocationCoordinate2D
in Swift from within the
MKAnnotationProtocol
context (a class extending
NSObject
). But, the same return type from a Kotlin class (not extending
NSObject
), doesn't return a
CLLocationCoordinate2D
. Instead, I (unexpectedly) get a `KotlinCValue<AnyObject>`:
Copy code
// in Swift, this returns a KotlinCValue<AnyObject>
var value = MockData().defaultMapLocation.asCoordinate()
Again, that's not a
CLLocationCoordinate2D
, and it won't cast to one. I can't figure out how turn it into one, either; I'm new to Kotlin/Native cinterop and my understanding is still incomplete. Maybe I need to use
Unmanaged
like the comments from https://youtrack.jetbrains.com/issue/KT-39407/KotlinNative-Swift-interop-iOS-CGImage suggest... I tried a few different iterations of that approach, unsuccessfully. Back to the original question What's the best "right" way to get the
CLLocationCoordinate2D
out of the
KotlinCValue<AnyObject>
? Or, to ask it another way, how can I have
Location.asCoordiante()
return a
CLLocationCoordinate2D
that Swift can use? I think this is related to improving Core Foundation support per https://youtrack.jetbrains.com/issue/KT-29256/Improve-support-for-Core-Foundation-types since Core Location is another C framework. I think the "right" way would be to convert my
Location
to an
expect
/
actual
that extends
NSObject
in
iosMain
, and conforms to a protocol for "asCoordinate" that I need to make and declare in a
nativeinterop
def
file (so that I can send the message to the
NSObject
to prevent "unrecognized selector sent to instance" errors, since
NSObject
subclasses appear to lose method definitions in the generated header file). But, what I'd really like to have happen is have this "just work" as-is keeping
Location
as a Kotlin data class, maybe with improved compiler support. It's unexpected to me that returning
CValue<CLLocationCoordinate2D>
"works" sometimes, but not others, and the documentation and discoverability isn't good enough yet to sort through it. For what it's worth, I've also found a different workaround doing a little more on the Kotlin side, but I don't think it's treating memory correctly (specifically, I'm not sure if the
MemScope()
below gives me an auto-released object that ARC captures on the Swift side of things to keep it around):
Copy code
// In commonMain LocationExtensions.kt - Exchange the CValue for a CPointer.
fun Location.asCoordinatePointer(): CPointer<CLLocationCoordinate2D> = asCoordinate().getPointer(MemScope())
I can get the
CLLocationCoordinate2D
in Swift by calling this as
location.asCoordinatePointer().load(as: CLLocationCoordinate2D.self)
.. but, again, this doesn't feel quite right to me. Another workaround, which I don't like, is to re-declare the method in Swift:
Copy code
// Location+Extensions.swift
extension Location {
    func asCoordinate() -> CLLocationCoordinate2D {
        return CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    }
}
This ends up making two
asCoordiante()
methods; the Kotlin one returning
KotlinCValue<AnyObject>
and the Swift one returning
CLLocationCoordinate2D
, and the compiler picks the right one based on the return type. This also duplicates the code itself (making the
CLLocationCoordinate2D
), and feels kind of gross. I guess a final way of asking this is: What is special about the
NSObject(), MKAnnotationProtocol
setup that let's me treat returning
CValue<CLLocationCoordinate2D>
as a
CLLocationCoordinate2D
in Swift, when a Kotlin data class that returns a
CValue<CLLocationCoordinate2D>
returns it to Swift as
KotlinCValue<AnyObject>
?
j
Hi, I encountered the exact same issue. Did you manage to return a
CValue<CLLocationCoordinate2D>
from the
asCoordinate
function inside the compiled objective-c code? I also posted my specific question here stackoverflow, I have pretty much the same setup as you did.
d
No, I never did figure this one out unfortunately. I'm still using the Swift extension workaround.
j
Ok, thanks for the quick reply. If I figure it out, I will let you know.
169 Views