Hi all, I’m currently having trouble converting a ...
# ios
s
Hi all, I’m currently having trouble converting a Kotlin
Map
to an objective-c
CFDictionary
using
CFDictionaryCreate
. The main issue is using the pointers to go back and forth between Kotlin and Objective-C. Here’s my code so far (not generic for now will be later)
Copy code
fun Map<CFStringRef?, Any?>.toCFDictionary(): CFDictionaryRef? {
    return CFDictionaryCreate(
        kCFAllocatorDefault,
        keys,
        values,
        size.toLong(),
        kCFTypeDictionaryKeyCallBacks,
        kCFTypeDictionaryValueCallBacks
    )
}
There is a type mismatch for the second, third, fifth and sixth arguments. Second and third arguments should be CValuesRef<COpaquePointerVar>? but are currently Set<CPointer<__CFString>?> and Collection<Any?>. The last two arguments should be CValuesRef<CFDictionaryKeyCallBacks>? and CValuesRef<CFDictionaryValueCallBacks>? but are CFDictionaryKeyCallBacks and CFDictionaryValueCallBacks. I do not know how to convert those values to the expected ones, can anyone help me plase ? Thank you for your time.
s
I’m not in front of a computer right now. If you search in this slack for examples of working with the iOS keychain, you’ll find examples of how to deal with a CFDictionary.
s
There are some, however I cannot find any for bridging
Any?
to
CValuesRef<*>?
. Is it because I’m not supposed to do that ? The update code is now
Copy code
fun Map<CFStringRef?, Any?>.toCFDictionary(): CFDictionaryRef? {
    memScoped {
        val mutableResult = CFDictionaryCreateMutable(
            kCFAllocatorDefault,
            size.toLong(),
            null,
            null
        )
        entries.forEach { entry ->
            CFDictionaryAddValue(
                mutableResult,
                entry.key,
                entry.value
            )
        }
        return mutableResult
    }
}
entry.value
is the only error left.
s
Here’s the function I have that does that. I don’t think I actually came up with it but cribbed it together from various sources.
Copy code
/**
 * Converts a mutable map into a CFDictionaryRef.  This function should only be called within a memScoped block.
 */
private fun MutableMap<CFStringRef, Any>.toCFDictionary() = CFDictionaryCreateMutable(null, this.keys.size.toLong(), null, null).also { cfd ->
    this.entries.forEach { entry ->
        when(val value = entry.value) {
            is String -> CFDictionaryAddValue(cfd, entry.key, CFStringCreateWithCString(null, value, kCFStringEncodingUTF8))
            is Int -> CFDictionaryAddValue(cfd, entry.key, CFBridgingRetain(NSNumber(int = value)))
            is Long -> CFDictionaryAddValue(cfd, entry.key, CFBridgingRetain(NSNumber(long = value)))
            is Float -> CFDictionaryAddValue(cfd, entry.key, CFBridgingRetain(NSNumber(float = value)))
            is Double -> CFDictionaryAddValue(cfd, entry.key, CFBridgingRetain(NSNumber(double = value)))
            is CPointer<*> -> CFDictionaryAddValue(cfd, entry.key, value)
        }
    }
}
The reason for the NSNumber conversions is that you can’t store primitive values in a CFDictionary, it only takes objects. IIRC, it interprets the numbers as pointers and then all hell breaks loose.
s
Awesome ! Thank you very much ! I still have a lot to learn, coming from Android and iOS Native and so far, the biggest problem that I encountered is everything about the pointers and the memory management. That’s what I get for using Swift, I guess. Thanks again !
r
I also do similar stuff in Multiplatform Settings if you want another example: https://github.com/russhwolf/multiplatform-settings/blob/master/multiplatform-settings/src/appleMain/kotlin/com/russhwolf/settings/KeychainSettings.kt The
cfRetain()
function takes a list of arbitrary values and gives you a block where they've been passed through
CFBridgingRetain()
to get `CFTypeRef`s, and then calls
CFBridgingRelease
on them all for you after the block runs. The
cfDictionaryOf()
function converts a
Map<CFStringRef?, CFTypeRef?>
to a
CFDictionaryRef
. I probably got a little too cute with the API given that it's all private stuff in one file, but it's maybe a helpful pattern for wrapping some of the uglier pointer handling so your calling API can be more Kotliny
s
Great ! Thanks a lot !