Search code examples
iosswiftcocoapodskotlin-multiplatformkotlin-interop

How to adjust Swift classnames for Kotlin Multiplatform project with Cocoapods?


In my Kotlin multiplatform project i heavily use namespaces. Also since i'm using MVP i have similar classnames. eg:

com.company.project.usecase1.Model
com.company.project.usecase1.View
com.company.project.usecase1.Presenter
com.company.project.usecase2.Model
com.company.project.usecase2.View
com.company.project.usecase2.Presenter

Now i want to use it in iOS app (written in Swift). So i've added iOS target to build.gradle - everything like in example. I was able to generate Cocoapod (gradlew podspec) and use it Swift app.

Related part of build.gradle:

version = "$rootProject.module_version"

kotlin {
    cocoapods {
        summary = "App MVP of NotesClientApp"
        homepage = "some url"
    }
}

However since Swift does not have namespaces and classnames look similar generated obj-c wrapper looks ugly: it uses artificial mutations (underline) in classnames just to distinguish the names, eg.

__attribute__((swift_name("Presenter__")))
@protocol App_mvpPresenter__ <App_mvpBasePresenter>
@required
@end;

__attribute__((swift_name("View__")))
@protocol App_mvpView__ <App_mvpBaseView>
@required
- (void)showValidationErrorError:(App_mvpKotlinException *)error __attribute__((swift_name("showValidationError(error:)")));
- (void)showNotesList __attribute__((swift_name("showNotesList()")));
@property NSString *host __attribute__((swift_name("host")));
@property NSString *port __attribute__((swift_name("port")));
@end;

__attribute__((swift_name("Model__")))
@interface App_mvpModel__ : KotlinBase
- (instancetype)initWith_host:(NSString * _Nullable)_host _port:(App_mvpUInt * _Nullable)_port __attribute__((swift_name("init(_host:_port:)"))) __attribute__((objc_designated_initializer));
- (void)updateHost:(NSString *)host port:(uint32_t)port __attribute__((swift_name("update(host:port:)")));
@property (readonly) NSString * _Nullable host __attribute__((swift_name("host")));
@property (readonly) App_mvpUInt * _Nullable port __attribute__((swift_name("port")));
@property id<App_mvpPresenter__> _Nullable presenter __attribute__((swift_name("presenter")));
@end;

__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("PresenterImpl__")))
@interface App_mvpPresenterImpl__ : KotlinBase <App_mvpPresenter__>
- (instancetype)initWithModel:(App_mvpModel__ *)model __attribute__((swift_name("init(model:)"))) __attribute__((objc_designated_initializer));
- (void)attachViewView:(id<App_mvpView__>)view __attribute__((swift_name("attachView(view:)")));
- (void)onModelChanged __attribute__((swift_name("onModelChanged()")));
- (void)onViewChanged __attribute__((swift_name("onViewChanged()")));
- (void)onViewDetached __attribute__((swift_name("onViewDetached()")));
@property (readonly) App_mvpModel__ *model __attribute__((swift_name("model")));
@end;

I guess there should be some possibility to adjust the behaviour: add some config file or annotations for naming adjustments.

Any other possibility to rename classes in Obj-c/Swift not related to Kotlin? i've tried to use Swift typealiases, but got "typealias invalid redeclaration" error only.

Any thoughts?

PS. Also i can see module name (app-mvp) works as prefix, eg. classname App_mvpView__ - any possibility to adjust generated Framework name without changing of Gradle module name (since i still want to use proper JVM build artifact names: app-mvp-jvm.jar)?

PPS. I do understand it could be easier just rename classes to make them unique in all namespaces, but anyway.


Solution

  • At this time it isn't possible to override the class names for native. JavaScript MPP has an annotation called JSName so I wouldn't rule it out in the future. To change your framework name you can simply set the baseName value in your framework configuration under targets.

        fromPreset(iOSTarget, 'ios') {
            binaries {
                framework {
                    baseName = "MyFrameworkName"
                }
            }
        }
    

    If you're using the packForXcode task you will likely need to update it to find your new framework. Mine looks like this:

    task packForXCode(type: Sync) {
        final File frameworkDir = new File(buildDir, "xcode-frameworks")
        final String mode = (project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG').split("_").first()
    
    
        inputs.property "mode", mode
        def bin = kotlin.targets.ios.compilations.main.target.binaries.findFramework("", mode)
        dependsOn bin.linkTask
    
        from bin.outputDirectory
        into frameworkDir
    
        doLast {
            new File(frameworkDir, 'gradlew').with {
                text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$@\n"
                setExecutable(true)
            }
        }
    }
    

    Mine also has a customization to support multiple project schemes in Xcode. I have one for each environment (dev, test, prod) the app can point to.