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?
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;
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)
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With