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@NSAutoreleasePool
Jeff 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