如何为 iOS 制作一个多模块的 kotlin 多平台库?
How to make a multi-module kotlin multiplatform library for iOS?
我正在尝试制作一个 Kotlin Multiplatform 库,以便在 Android、JavaScript 和 iOS 中使用。该库由多个模块组成,因此可以轻松扩展。
我现在的问题只是 Kotlin native。
项目是这样设置的
- 一个模块
:common
object MySingleton {
fun doSomethingWithMyInterface(value: MyInterface) {
// ...
}
}
interface MyInterface {
fun doSomething()
}
- 实现
MyInterface
的其他模块
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 不支持生成相互依赖的二进制框架。目前仅支持两种工作配置:
- 使用多个独立的 K/N 生成的框架;或
- 将所有 Kotlin 模块导出为一个整体 K/N 生成的框架(在社区中也称为“伞形框架”方法)。
如果您打算将代码组织到 Kotlin 模块的层次结构中,(2) 是您唯一的选择。你要么:
- 仅使用来自
example
模块的框架产品,其框架产品已经包含其所有依赖项的所有代码(包括传递性代码和 Kotlin 运行时),正如您可能已经观察到的那样;或
- 创建一个新的,例如
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.
我正在尝试制作一个 Kotlin Multiplatform 库,以便在 Android、JavaScript 和 iOS 中使用。该库由多个模块组成,因此可以轻松扩展。 我现在的问题只是 Kotlin native。
项目是这样设置的
- 一个模块
:common
object MySingleton {
fun doSomethingWithMyInterface(value: MyInterface) {
// ...
}
}
interface MyInterface {
fun doSomething()
}
- 实现
MyInterface
的其他模块
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 不支持生成相互依赖的二进制框架。目前仅支持两种工作配置:
- 使用多个独立的 K/N 生成的框架;或
- 将所有 Kotlin 模块导出为一个整体 K/N 生成的框架(在社区中也称为“伞形框架”方法)。
如果您打算将代码组织到 Kotlin 模块的层次结构中,(2) 是您唯一的选择。你要么:
- 仅使用来自
example
模块的框架产品,其框架产品已经包含其所有依赖项的所有代码(包括传递性代码和 Kotlin 运行时),正如您可能已经观察到的那样;或 - 创建一个新的,例如
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.