如何为 iOS 制作一个多模块的 kotlin 多平台库?

How to make a multi-module kotlin multiplatform library for iOS?

我正在尝试制作一个 Kotlin Multiplatform 库,以便在 Android、JavaScript 和 iOS 中使用。该库由多个模块组成,因此可以轻松扩展。 我现在的问题只是 Kotlin native。

项目是这样设置的

object MySingleton {
    fun doSomethingWithMyInterface(value: MyInterface) {
        // ...
    }
}

interface MyInterface {
  fun doSomething()
}
class MyInterfaceExample : MyInterface {
    override fun doSomething() {
        // ...
    }
}

我已经为 :common 设置了 build.gradle.kts 文件,如下所示:

plugins {
    kotlin("multiplatform")
    kotlin("native.cocoapods")
    id("maven-publish")
}

kotlin {
    targets {
        jvm()
        js().browser()
        ios("ios") {
            binaries {
                framework("CommonModule") {
                    baseName = "common"
                }
            }
        }
    }

    cocoapods {
        frameworkName = "CommonModule"
        summary = "My common module"
        homepage = "-"
        license = "-"

        ios.deploymentTarget = "10.0"
    }

    sourceSets {
        val iosX64Main by getting
        val iosArm64Main by getting
        val iosMain by getting {
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
        }
    }
}

我的其他模块如下:

plugins {
    id("com.android.library")
    id("maven-publish")
    kotlin("multiplatform")
    kotlin("native.cocoapods")
}

android {
    compileSdkVersion(30)
}

kotlin {
    targets {
        js().browser()
        ios("ios") {
            binaries {
                framework("ExampleImplementation", listOf(DEBUG)) {
                    baseName = "example"
                    freeCompilerArgs += "-Xobjc-generics"
                    export(project(":common")) {
                        isStatic = true
                    }
                }
            }
        }
        android("android") {
            publishLibraryVariants("release")
        }
    }

    cocoapods {
        frameworkName = "ExampleImplementation"
        summary = "Example implementation of MyInterface"
        homepage = "-"
        license = "-"

        useLibraries()

        ios.deploymentTarget = "10.0"
        pod("MyDependency")
    }

    sourceSets {
        val commonMain by getting {
            dependencies {
                api(project(":common"))
            }
        }
        val iosX64Main by getting
        val iosArm64Main by getting
        val iosMain by getting {
            dependsOn(commonMain)
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
        }
        val androidMain by getting {
            dependsOn(commonMain)
            dependencies {
                implementation("group:artifactId:1.0.0")
            }
        }
        val jsMain by getting {
            dependencies {
                implementation(npm("my_dependency", "1.0.0"))
            }
        }
    }
}

:common:example 模块都会生成一个 podspec 文件,其中添加了一个由 cocoapods 管理器执行的脚本:

<<-SCRIPT
                set -ev
                REPO_ROOT="$PODS_TARGET_SRCROOT"
                "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" :example:syncFramework \
                    -Pkotlin.native.cocoapods.target=$KOTLIN_TARGET \
                    -Pkotlin.native.cocoapods.configuration=$CONFIGURATION \
                    -Pkotlin.native.cocoapods.cflags="$OTHER_CFLAGS" \
                    -Pkotlin.native.cocoapods.paths.headers="$HEADER_SEARCH_PATHS" \
                    -Pkotlin.native.cocoapods.paths.frameworks="$FRAMEWORK_SEARCH_PATHS"
            SCRIPT

当我像这样在 iOS 项目中添加这些模块时:

pod 'common', :path => '~/Project/MyLibrary/common/'
pod 'example', :path => '~/Project/MyLibrary/example/'

在我执行 pod install 之后,我可以看到两个框架都被添加了。

问题

example 生成的框架不依赖于为 common 生成的框架。然而,:example 生成了一个名为 ExampleCommonMyInterface 的自己的接口,它与 :common 中的 MyInterface 具有相同的签名。

此外,它们都包含一些 KotlinBase classes,这使我无法构建,因为我遇到了重复 classes 的问题。

我试图只包含 :example 但它缺少我的 MySingleton class (这不是 Swift 中的单例,但这是另一个问题)和其他 classes :example 不直接依赖。 此外,在某些时候,我希望有多个模块取决于 :common,因此仅包含 :example 只能暂时工作。

几天来我已经尝试了很多东西,但是 none 其中一些是有效的。此外,文档分散在 kotlin multiplatform 和 kotlin native 的文档之间,这让我很难找到关于我想要实现的目标的相关信息。

我知道 Kotlin 多平台仍然不稳定,我想要实现的目标目前可能无法实现。不过,如果有人能阐明问题所在以及我该如何解决,我将不胜感激。

目前 (Kotlin 1.5-M1),Kotlin/Native 不支持生成相互依赖的二进制框架。目前仅支持两种工作配置:

  1. 使用多个独立的 K/N 生成的框架;或
  2. 将所有 Kotlin 模块导出为一个整体 K/N 生成的框架(在社区中也称为“伞形框架”方法)。

如果您打算将代码组织到 Kotlin 模块的层次结构中,(2) 是您唯一的选择。你要么:

  1. 仅使用来自 example 模块的框架产品,其框架产品已经包含其所有依赖项的所有代码(包括传递性代码和 Kotlin 运行时),正如您可能已经观察到的那样;或
  2. 创建一个新的,例如iosExport 导出所有模块的模块,这些模块应该 API 对您的 iOS 代码库可见。

参考

这方面的问题跟踪改进: https://youtrack.jetbrains.com/issue/KT-42247

官方 KMM 文档已简要提及此主题:

https://kotlinlang.org/docs/mpp-build-native-binaries.html#export-dependencies-to-binaries [...] For example, assume that you write several modules in Kotlin and then want to access them from Swift. Since usage of several Kotlin/Native frameworks in one Swift application is limited, you can create a single umbrella framework and export all these modules to it.