Hi, I’m trying to create a multiplatform library f...
# multiplatform
h
Hi, I’m trying to create a multiplatform library for both iOS and Android, and the approach we’ve taken is to delegate out some of the details to the host apps. We expect them to implement certain interfaces and provide concrete classes of this interface to the library. However we have run into an issue. Given the following kotlin interface
Copy code
interface KeyValueStorage {
    @Throws(Throwable::class) fun get(key: String): String?
}
This generates the following in objective-c:
Copy code
- (NSString * _Nullable)getKey:(NSString *)key error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("get(key:)")));
When you try to implement this in swift (as generated by xcode)
Copy code
final class SampleStorage: KeyValueStorage {
    func get(key: String) throws -> String? { 
        return ""
    }
}
This function gives this compile error:
Copy code
Throwing method cannot be an implementation of an @objc requirement because it returns a value of type 'String?'; return 'Void' or a type that bridges to an Objective-C class
Does anybody has any idea what the issue is, how to solve this, or have alternative suggestions?
v
h
This does not solve my problem. It actually is a feature of the library that the host app can decide how and where to store the data required by the library. Moving the storage away from the host app into the library heavily limits usage of the library we’re building.
h
Thank you for your reply. This is indeed the limitation we’re running into. Right now we’re struggling a bit, since throwing functions that return a boolean are also not working 🤔 . Ill update this thread once we’ve figured it out
r
What exactly is the issue with functions returning booleans? Are they missing the return type in Swift?
h
We’re figuring it out now. Our current hunch is that a method named
@Throws(Throwable::class) contains(key: String): Boolean
conflicts with some protected keywords in objective-c (but we’re trying that now)
nevermind, it just gives another error on the ios compile side:
Throwing method cannot be an implementation of an @objc requirement because it returns a value of type 'Bool'; return 'Void' or a type that bridges to an Objective-C class
r
Alright. I didn’t see the
swift_error
attribute in your ObjC, that could also explain something (if it really isn’t present).
h
personally i think the issue is that void returning throwing functions are translated as BOOL returning functions in objective c, so that conflicts when you have a boolean returning throwing function.
r
So a
Unit
returning function in Kotlin gives a
BOOL
returning ObjC method? The
swift_error
attribute should be able to fix Swift from thinking a
BOOL
return value is used for the error. But I am not sure if the Kotlin compiler actually adds it to the generated header.
Hmm looks like
swift_error
is only added if the return type is nullable.
h
Hmm, im a bit lost. Some examples: void returning throwing function kotlin:
@Throws(Throwable::class) fun delete(key: String)
objective c:
- (*BOOL*)deleteKey:(NSString *)key error:(NSError * *_Nullable* * *_Nullable*)error *__attribute__*((swift_name("delete(key:)")));
swift:
*func* delete(key: String) *throws* { }
object returning throwing function kotlin:
@Throws(Throwable::class) fun get(key: String): String
objective c:
- (NSString * *_Nullable*)getKey:(NSString *)key error:(NSError * *_Nullable* * *_Nullable*)error *__attribute__*((swift_name("get(key:)")));
swift:
*func* get(key: String) *throws* -> String { }
boolean returning throwing function kotlin:
@Throws(Throwable::class) fun contains(key: String): Boolean
objective-c:
- (*BOOL*)contains:(NSString *)key error:(NSError * *_Nullable* * *_Nullable*)error *__attribute__*((swift_name("contains(key:)"))) *__attribute__*((swift_error(nonnull_error)));
swift:
*func* contains(key: String) *throws* -> Bool { }
yields the error:
Throwing method cannot be an implementation of an @objc requirement because it returns a value of type 'Bool'; return 'Void' or a type that bridges to an Objective-C class
I suspect this also is a limitation in the swift <-> objecitve-c interop
r
The generated ObjC/Swift looks good. Seems the Swift ObjC interop only supports importing declarations with a custom error handling behaviour (and not implementing them).
h
So, after learning the limitations of the Swift <-> Objective-C interop, we had to do some very implementation specific changes to our interface to be able to support all situations required. These are the limitations we discovered so far: • throwing functions cannot have optional (nullable) return types • throwing functions cannot return Booleans
r
Any specific approach you took to overcome these limitations? Or did you just wrap the return types?
h
I didnt wrap the return types, i was about to but that just feels dirty. Instead I have decided to change the method signature for the
contains(key: String): Boolean
method so it no longer throws. So now it really only has 2 return options, either true or false (not true, false, exception anymore). So now the
contains
method returns true if it is absolutely sure the value exists, and false if it is sure it does not exist, or some exception occurred (i.e. read write issues, storage being locked, or whatever propriatary checks or encryption the host app wishes to do). And then I splitted the value getters in 2 methods: •
@Throws(Throwable::class) getOrThrow(key: String): String
which throws an exception if the value does not exist or if some issues with the underlying storage has occurred •
@Throws(Throwable::class) getOrDefault(key: String, valueIfAbsent: String): String
which still throws exceptions, but only if some issues with the underlying storage has occurred. If the value is just absent, it will return the default value But as i said, this is a very domain specific solution, I think, every time you run into these limitations there are different considerations for the best solution.
a
why don't you write the implementations in
iosMain
sourceSets
of the library???
h
Its not in the scope of the library nor the responsibility of the library im developing to provide implementations for certain interfaces.
this library will be used by multiple apps, so it cannot be tailored to one specific app but rather to multiple different apps with a different legacy codebases (one is even written in xamarin, for extra complexity)
For some specific situations we have defined platform specific interfaces (using expect / actual interfaces), but that results in very buggy testability with unit tests. (ill dive deeper in that and maybe post another question / thread about it if i cant get it to work properly)
a
If this libary is to be used in a KMM approach, then it is either the umbrella framework, or it will be consumed by the umbrella framework. I would advise the implementation should go into the umbrella framework of the app on the
iosMain
side of things
if the library happens to be the umbrella framework, and you still need it to be isolated, then you need some restructuring
h
The idea is that this project yields a platform native (-ish) library to be used without knowing that it was originally created with kotlin multiplatform. The IOS project using this library will just load an XCFramework (via SPM, we got that to work (i think)), in which they can just use this library as if it was a regular framework