Is it not possible to declare additional `initWith...
# kotlin-native
j
Is it not possible to declare additional
initWith...
functions (constructors) in an ObjC category in a .def file? I've had success declaring properties and functions that aren't in the public API of an ObjC class, but adding an
initWith...
function doesn't add the constructor definition (or the
@Deprecated initWith...
function that is usually generated with the constructor). For example, where
MyObjCClass
is previously declared in a public API header:
Copy code
@interface MyObjCClass (Internal)
@property (nonatomic, readonly, nonnull) NSString *foo;
- (BOOL) isBar;
- (instancetype) initWithFoo: (NSString)foo bar: (BOOL)bar;
@end
Adds extension functions on the class definition, instead of modifying the class defined by the public API header:
Copy code
expect val MyObjCClass.foo: NSInteger
@ObjCCallable external expect fun MyObjCClass.foo(): NSInteger
@ObjCCallable external expect fun MyObjCClass.isBar(): Boolean
There is nothing added for the
initWithFoo:bar:
constructor though.
I ended up coming up with a complicated workaround to temporarily insert the
initWith...
constructor declaration into the library's public API header for the class, immediately before the C interop task, so that it's added to the Kotlin class definition.
r
How about when you use
@interface MyObjCClass ()
?
j
Same behavior. It seems Kotlin treats ObjC categories and extensions the same way. Properties and functions are added as extensions in Kotlin. Constructors are ignored.
r
Hmm alright. Another workaround would be to add a static "wrapper function" to the header.
j
Could you explain that some more? I tried something like this:
Copy code
static inline initMyObjCClass(NSString* foo, BOOL bar) {
    return [[MyObjCClass alloc] initWithFoo:foo bar:bar];
}
And get the error:
error: no visible @interface for 'MyObjCClass' declares the selector 'initWithFoobar'
Or do you mean adding it directly to the original header, not the .def file?
r
Yeah no I meant the .def file. Did you still declare the init inside the internal interface?
Something like
Copy code
@interface MyObjCClass (Internal)
- (instancetype) initWithFoo: (NSString)foo bar: (BOOL)bar;
@end

MyObjCClass* MyObjCClass_initWithFoo(NSString* foo, BOOL bar) {
    return [[MyObjCClass alloc] initWithFoo:foo bar:bar];
}
Then you could use
MyObjCClass_initWithFoo
from Kotlin. It’s not as nice, but likely less work then modifying the original header.
j
Ah yes, this does seem to work. Thanks for the suggestion. It's a bit cleaner workaround.
👍🏻 1
Testing this more, it seems this isn't just an issue with extensions defined in the .def file. This is the behavior for any constructors defined in a category or extension in the header files as well. This probably makes sense, since the .def declarations are treated the same as headers.
One limitation to the static function workaround is that it doesn't handle some ObjC type ARC conversion. For example, an
NSError*
parameter creates the error:
error: implicit conversion of an Objective-C pointer to 'NSError *__autoreleasing _Nullable * _Nullable' is disallowed with ARC