Search code examples
androidiosswiftkotlinkotlin-multiplatform

How to pass bytes from Swift (iOS) to Kotlin common module?


To share a implementation of a protocol between an android and iOS app I am experimenting with Kotlin Multiplatform. I set up a basic multiplatform project as described here.

It defines common code in the shared module ...

fun createApplicationScreenMessage() : String {
  return "Kotlin Rocks on ${platformName()}"
}

... which can be used in an iOS Project CommonKt.createApplicationScreenMessage()

Now I would like to do IO operations in the common module. I found Kotlinx-io for this and can use it inside the common module.

But how do I design the api between kotlin code and swift code properly, so I can pass InputStream/ByteArray/ByteReadPacket equivalents from Swift to the kotlin module?

e.g. something like this with kotlinx-io types like ByteReadPacket:

Kotlin common module:

class ProtocolReader{
    public fun parse(packet: ByteArray): ParsedMessage {
    //parse data
    } 
}

Swift iOS App

var byteArray = [UInt8](characteristicData)
let reader = ProtocolReader()
reader.parse(byteArray)

This example does not work because swift byteArray is not interoperable with KotlinByteArray.

How do I achieve this? Do I need to define api endpoints for each platform e.g. in this case in the ios module of the Kotlin multiplatform project? or are there helper methods to create kotlinx-io data types from ios data types?


Solution

  • Everything you pass up to your common code needs to be platform agnostic, so either you define a model using the expect/actual mechanism or you map your swift data types into kotlin data types.

    I am not fluent in swift, but you could do something like this:

    let swiftByteArray : [UInt8] = []
    let intArray : [Int8] = swiftByteArray
        .map { Int8(bitPattern: $0) }
    let kotlinByteArray: KotlinByteArray = KotlinByteArray.init(size: Int32(swiftByteArray.count))
    for (index, element) in intArray.enumerated() {
        kotlinByteArray.set(index: Int32(index), value: element)
    }
    

    Looking at the generated interoperability headers also helps sometimes.

    KotlinByte:

    __attribute__((objc_runtime_name("KotlinByte")))
    __attribute__((swift_name("KotlinByte")))
    @interface MainByte : MainNumber
    - (instancetype)initWithChar:(char)value;
    + (instancetype)numberWithChar:(char)value;
    @end;
    

    KotlinByteArray:

    __attribute__((objc_subclassing_restricted))
    __attribute__((swift_name("KotlinByteArray")))
    @interface MainKotlinByteArray : KotlinBase
    + (instancetype)arrayWithSize:(int32_t)size 
    __attribute__((swift_name("init(size:)")));
    + (instancetype)arrayWithSize:(int32_t)size init:(MainByte *(^)(MainInt *))init 
    __attribute__((swift_name("init(size:init:)")));
    + (instancetype)alloc __attribute__((unavailable));
    + (instancetype)allocWithZone:(struct _NSZone *)zone. __attribute__((unavailable));
    - (int8_t)getIndex:(int32_t)index __attribute__((swift_name("get(index:)")));
    - (MainKotlinByteIterator *)iterator __attribute__((swift_name("iterator()")));
    - (void)setIndex:(int32_t)index value:(int8_t)value 
    __attribute__((swift_name("set(index:value:)")));
    @property (readonly) int32_t size;
    @end;