Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 577
Sven Avatar asked Dec 18 '22 17:12

Sven


2 Answers

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;
like image 123
Tobi Avatar answered Dec 21 '22 10:12

Tobi


I ended up making this more functional

extension KotlinByteArray {
    static func from(data: Data) -> KotlinByteArray {
        let swiftByteArray = [UInt8](data)
        return swiftByteArray
            .map(Int8.init(bitPattern:))
            .enumerated()
            .reduce(into: KotlinByteArray(size: Int32(swiftByteArray.count))) { result, row in
                result.set(index: Int32(row.offset), value: row.element)
            }
    }
}
like image 43
Stefan Avatar answered Dec 21 '22 11:12

Stefan