如何在 iOS 项目上添加两个或更多 kotlin 原生模块

How to add two or more kotlin native modules on an iOS project

TL;DR;

如何在 iOS 项目中添加两个或多个 kotlin 原生模块而不出现 duplicate symbols 错误?

详细问题

让我们假设一个多模块 KMP 项目如下,其中存在一个用于 Android 的本机应用程序和一个用于 iOS 的本机应用程序以及两个用于保存共享 kotlin 代码的通用模块。

.
├── android
│   └── app
├── common
│   ├── moduleA
│   └── moduleB
├── ios
│   └── app

模块A包含一个数据class HelloWorld并且没有模块依赖:

package hello.world.modulea

data class HelloWorld(
    val message: String
)

模块 B 包含 HelloWorld 的扩展函数 class 所以它依赖于模块 A:

package hello.world.moduleb

import hello.world.modulea.HelloWorld

fun HelloWorld.egassem() = message.reversed()

模块的build.gradle配置为:

apply plugin: "org.jetbrains.kotlin.multiplatform"
apply plugin: "org.jetbrains.kotlin.native.cocoapods"

…

kotlin {
    targets {
        jvm("android")

        def iosClosure = {
            binaries {
                framework("moduleA")
            }
        }
        if (System.getenv("SDK_NAME")?.startsWith("iphoneos")) {…}
    }

    cocoapods {…}

    sourceSets {
        commonMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72"
        }
        androidMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.72"
        }
        iosMain.dependencies {
        }
    }
}
apply plugin: "org.jetbrains.kotlin.multiplatform"
apply plugin: "org.jetbrains.kotlin.native.cocoapods"
…

kotlin {
    targets {
        jvm("android")

        def iosClosure = {
            binaries {
                framework("moduleB")
            }
        }
        if (System.getenv("SDK_NAME")?.startsWith("iphoneos")) {…}
    }

    cocoapods {…}

    sourceSets {
        commonMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72"
            implementation project(":common:moduleA")
        }
        androidMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.72"
        }
        iosMain.dependencies {
        }
    }
}

它看起来非常简单,如果我按如下方式配置 android build gradle 依赖项,它甚至可以在 android 上运行:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72"
    implementation project(":common:moduleA")
    implementation project(":common:moduleB")
}

但是,这似乎不是在 iOS 上组织多模块的正确方法,因为 运行 宁 ./gradlew podspec 我得到了预期的 BUILD SUCCESSFUL具有以下 pods:

pod 'moduleA', :path => '…/HelloWorld/common/moduleA'
pod 'moduleB', :path => '…/HelloWorld/common/moduleB'

即使 运行 宁 pod install 我得到一个成功的输出 Pod installation complete! There are 2 dependencies from the Podfile and 2 total pods installed. 一旦 Xcode 在 [=79 上显示模块 A 和模块 B,什么看起来正确=] 部分。

但是,如果我尝试构建 iOS 项目,我会收到以下错误:

Ld …/Hello_World-…/Build/Products/Debug-iphonesimulator/Hello\ World.app/Hello\ World normal x86_64 (in target 'Hello World' from project 'Hello World')
    cd …/HelloWorld/ios/app
…
duplicate symbol '_ktypew:kotlin.Any' in:
    …/HelloWorld/common/moduleA/build/cocoapods/framework/moduleA.framework/moduleA(result.o)
    …/HelloWorld/common/moduleB/build/cocoapods/framework/moduleB.framework/moduleB(result.o)
… a lot of duplicate symbol more …
duplicate symbol '_kfun:kotlin.throwOnFailure$stdlib@kotlin.Result<#STAR>.()' in:
    …/HelloWorld/common/moduleA/build/cocoapods/framework/moduleA.framework/moduleA(result.o)
    …/HelloWorld/common/moduleB/build/cocoapods/framework/moduleB.framework/moduleB(result.o)
ld: 9928 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

我在 iOS 方面的知识并不多,所以在我未经训练的眼中,看起来每个模块都在添加它自己版本的东西,而不是使用一些解析策略来共享它。

如果我只使用模块 A,代码可以正常工作 运行,所以我知道代码本身是正确的,问题是如何管理 1 个以上的模块,所以问题是,如何在 iOS 上同时添加(模块 A 和模块 B)并使其正常工作?

P.S

我确实尽可能地减少了代码,试图只保留我认为是问题根源的部分,但是,如果您想检查任何内容,可以使用完整的代码 here片段中缺少,或者如果您想 运行 并尝试解决问题...

多个 Kotlin 框架可能会很棘手,但应该从 1.3.70 开始工作,我看到你有。

问题似乎是这两个框架都是静态的,这是目前 1.3.70 中的一个问题,因此无法正常工作。 (这应该在 1.40 之前更新)。看起来默认情况下 cocoapods 插件将框架设置为静态的,这是行不通的。我不知道有什么方法可以更改 cocoapods 以将其设置为动态,但我已经测试了没有 cocoapods 的构建并在 gradle 任务中使用 isStatic 变量,并且得到了 iOS要编译的项目。类似于:

binaries {
    framework("moduleA"){
        isStatic = false
    }
}

现在您可以通过使用上面的代码并创建一个任务来构建框架来解决这个问题(here's an example)

另一件值得注意的事情是,在 iOS 方面,HelloWorld 类 将显示为两个独立的 类,尽管它们都来自模块 A。这是另一个使用多个 Kotlin 框架的奇怪情况,但我认为扩展在这种情况下仍然有效,因为你返回的是一个字符串。

我实际上刚刚写了一篇关于多个 Kotlin 框架的博客 post,如果您想看一看,它可能有助于解决其他一些问题。 https://touchlab.co/multiple-kotlin-frameworks-in-application/

EDIT:看起来 cocoapodsext 也有一个 isStatic 变量,因此将其设置为 isStatic = false

tl:dr 您目前不能在同一 iOS 项目中拥有多个静态 Kotlin 框架。使用 isStatic = false.

将它们设置为非静态

However, if I try to build the iOS project I get the following error:

此特定错误是一个已知问题。多个调试静态框架与编译器缓存不兼容。

因此,要解决此问题,您可以通过将以下行放入 gradle.properties:

来禁用编译器缓存
kotlin.native.cacheKind=none

或者通过将以下代码片段添加到您的 Gradle 构建脚本来使框架动态化:

kotlin {
    targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
        binaries.withType<org.jetbrains.kotlin.gradle.plugin.mpp.Framework> {
            isStatic = false
        }
    }
}

有关详细信息,请参阅 https://youtrack.jetbrains.com/issue/KT-42254

我猜多个框架的当前行为对于最初的主题启动者来说没有多大意义,我只是把我的答案放在这里以供可能遇到相同问题的任何人使用。

My knowledge in iOS is not that much, so to my untrained eyes, it looks like each module is adding its own version of the things instead of using some resolutions strategy to share it.

这正是它目前的工作方式。但是每个框架中的“事物的版本”都放在单独的独立命名空间中,因此应该不会出现链接错误,而您遇到的是错误。