Jacob Rhoda
06/19/2023, 3:15 PMJeff Lockhart
06/19/2023, 3:58 PMNSData in Apple-platform Kotlin code or convert NSData to and from ByteArray.
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)
}
}leandro
06/22/2023, 11:20 AMusePinned from your solution? as in:
fun ByteArray.toNSData() = usePinned {
NSData.create(it.addressOf(0), size.convert(), freeWhenDone = false)
}Jacob Rhoda
06/22/2023, 3:06 PM@NSAutoreleasePoolJeff Lockhart
06/22/2023, 6:37 PMusePinned 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:
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:
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.leandro
06/22/2023, 9:35 PM