この記事は、筆者のメモを基に、AIが構成・加筆を行ったものです。
KMPのプロジェクトで複数のモジュールを作成し、それぞれをiOS側で使えるようにしていました。build-logicを以下のように作成して、複数のモジュールから参照していたのですが、Skieを導入したタイミングで問題が発生しました。
import co.touchlab.skie.plugin.configuration.skieExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
open class KmpPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
plugins.apply("org.jetbrains.kotlin.multiplatform")
plugins.apply("com.android.library")
plugins.apply("co.touchlab.skie")
extensions.getByType(KotlinMultiplatformExtension::class).apply {
androidTarget {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
}
}
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64(),
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "iOSKmp${project.name}"
isStatic = true
}
}
sourceSets.apply {
commonMain.dependencies {
}
commonTest.dependencies {
implementation(libs.findLibrary("kotlin-test").get())
}
}
}
configureAndroidLibrary(this)
skieExtension.apply {
features {
enableSwiftUIObservingPreview.set(true)
}
}
}
}
}
アプリを実行すると、以下のようなエラーが発生しました:
objc[501]: Class IOSKSkieColdFlowIterator is implemented in both /private/var/containers/Bundle/Application/9E7BBDB1-E633-463F-AA33-552FFFAE156B/KotlinProject.app/KotlinProject.debug.dylib (0x10b479e30) and /private/var/containers/Bundle/Application/9E7BBDB1-E633-463F-AA33-552FFFAE156B/KotlinProject.app/KotlinProject.debug.dylib (0x10b479e30). This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.
objc[501]: Class IOSKSkieKotlinFlow is implemented in both
...
どうやら、複数のモジュールからそれぞれStaticなフレームワークを生成していることが原因のよう。
Kotlinの公式ドキュメントを確認してみると、実はこの問題について言及されていました:
For example, you implement several modules in Kotlin and want to access them from Swift. Usage of several Kotlin/Native frameworks in a Swift application is limited, but you can create an umbrella framework and export all these modules to it.
つまり、複数のKotlinモジュールをSwiftから利用する場合は、「アンブレラフレームワーク」として1つにまとめるべきということです。
試したこと
1. キャッシュを無効にする
まず、以下の設定を試してみました:
kotlin.native.cacheKind=none
これはYouTrackのissue(https://youtrack.jetbrains.com/issue/KT-42254)で提案されていた方法ですが、残念ながら私の環境では解決しませんでした。また、この方法はパフォーマンスにも悪影響があるようです。
2. 動的フレームワークに変更する
次に、静的フレームワークではなく動的フレームワークを使用する方法を検討しました:
isStatic = false
これは動作する可能性がありますが、アプリ申請時にはXcodeのBuild Phasesで「Embed & Sign」を正しく設定する必要があり、別の問題が発生する可能性があります。
3. アンブレラフレームワークを作成する(推奨解決策)
最終的に採用したのは、iOS用のエントリーポイントとなる専用モジュールを1つ作成し、そこから必要なモジュールをエクスポートする方法です。
以下のようにios-umbrella
モジュールを作成しました:
plugins {
id("composit.build.kmp")
}
kotlin {
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64(),
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "SharedIOSFramework"
isStatic = true
}
}
sourceSets.commonMain.dependencies {
api(projects.domain)
api(projects.common)
// 他の必要なモジュール
}
}
この方法で、iOS側に公開するフレームワークを1つにまとめることができました。
注意点
-
commonMain
にKotlinソースがないとコンパイラが反応しないため、ダミーの.kt
ファイルも必要です