Is there a Kotlin type that mirrors NSData?
# ios
j
Is there a Kotlin type that mirrors NSData?
j
You can use
NSData
in Apple-platform Kotlin code or convert
NSData
to and from
ByteArray
.
Copy code
fun ByteArray.toNSData() = memScoped {
    NSData.create(bytes = allocArrayOf(this@toNSData), length = size.convert())
}

fun NSData.toByteArray() = ByteArray(length.toInt()).apply {
    if (isNotEmpty()) {
        memcpy(refTo(0), bytes, length)
    }
}
l
Is there any differences when using
usePinned
from your solution? as in:
Copy code
fun ByteArray.toNSData() = usePinned {
  NSData.create(it.addressOf(0), size.convert(), freeWhenDone = false)
}
j
My understanding is that usePinned ensures Kotlin objects are pinned in memory within the scope of the lambda, but memScoped is like
@NSAutoreleasePool
j
Yes,
usePinned
guarantees the native memory address
it.addressOf(0)
is a valid pointer to the
ByteArray
and that the memory is not relocated for the lambda's scope. There's no guarantee once the scope is left though.
memScoped
provides a scope where you can allocate native memory and it will be freed at the end of the scope. The issue I see with your function is that it is actually using the
bytesNoCopy
version of the
NSData
constructor (which is unclear without explicitly using the named argument). There's no
freeWhenDone
argument in the version that does copy bytes, where the argument is
bytes
. Your function is the equivalent of:
Copy code
fun ByteArray.toNSData() = usePinned {
    NSData.create(bytesNoCopy = it.addressOf(0), length = size.convert(), freeWhenDone = false)
}
So internally the
NSData
is pointing to the `ByteArray`'s memory instead of copying it. But there's no guarantee that the
ByteArray
won't be relocated to another memory address or be garbage collected, invalidating the `NSData`'s memory.
freeWhenDone = false
only ensures the
NSData
won't take ownership of and free the `ByteArray`'s memory itself. In this case, the
NSData
is only safe to use within the scope of
usePinned
, which ends before the function returns. My implementation actually has two memory copies, once with the
allocArrayOf()
and again with the
NSData(bytes = ...)
constructor. I suppose an optimal implementation between our solutions, only copying the
ByteArray
memory once, would be:
Copy code
fun ByteArray.toNSData(): NSData {
    return if (isNotEmpty()) {
        usePinned {
            NSData.create(bytes = it.addressOf(0), length = size.convert())
        }
    } else {
        NSData.data()
    }
}
This also handles an empty
ByteArray
, where
it.addressOf(0)
will crash in this case.
l
thank you for this Jeff, I learned a lot 🙏 and BRB I have some bugfixing and tests to manage