android - 如何在 iOS 项目中添加两个或多个 kotlin 原生模块

标签 android ios kotlin gradle kotlin-multiplatform

TL;博士;

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

详细的问题

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

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

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

package hello.world.modulea

data class HelloWorld(
    val message: String
)

模块 B 包含 HelloWorld 类的扩展函数,因此它依赖于模块 A:

package hello.world.moduleb

import hello.world.modulea.HelloWorld

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

模块的 build.gradle 配置为:
  • 模块 A
  • 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 {
            }
        }
    }
    
  • 模块 B
  • 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正如预期的那样,使用以下 pod:

    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 在 Pods 部分显示模块 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 代码按预期工作和运行,所以我知道代码本身是正确的,问题是如何管理多个模块,所以问题是如何同时添加(模块 A 和模块 B ) 在 iOS 上并让一切正常运行?

    附言

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

    最佳答案

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

    问题似乎是这两个框架都是静态的,这目前是 1.3.70 中的一个问题,所以它不起作用。 (这应该由 1.40 更新)。看起来默认情况下 cocoapods 插件将框架设置为静态的,这将不起作用。我不知道如何更改 cocoapods 以将其设置为动态,但我已经测试了没有 cocoapods 并使用 isStatic 的构建gradle 任务中的变量,并且已经获得了要编译的 iOS 项目。就像是:

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

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

    另一件值得注意的事情是,在 iOS 端,HelloWorld 类将显示为两个独立的类,尽管它们都来自 moduleA。这是多个 Kotlin 框架的另一种奇怪情况,但我认为扩展在这种情况下仍然可以工作,因为您要返回一个字符串。

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

    编辑 : 看起来像 cocoapodsext还有一个 isStatic变量,因此将其设置为 isStatic = false
    tl:dr 您目前在同一个 iOS 项目中不能有多个静态 Kotlin 框架。使用 isStatic = false 将它们设置为非静态.

    关于android - 如何在 iOS 项目中添加两个或多个 kotlin 原生模块,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61469574/

    相关文章:

    android - Anko布局DSL : How to use already existing layout?

    java - 在运行时更新 Activity 的 fragment , View 变量不是全局的

    ios - 使用填充有字母的 UiButtons 来造词

    java - 从 Java 调用 Kotlin 时如何获取错误的行号?

    android - 如何在 Firebase 中存储 BigDecimal?

    java - 当子字符串包含某些字符时更改整个字符串

    java - Android 中的动画性能缓慢

    iphone - 如何在 iPhone sdk 中使用 AVMutableComposition 合并视频

    iphone - 闹钟iOS

    swift - 尝试在 Swift 中实现 ?.let 模式,有可能吗?