环境

MacOS 15.6、Xcode 16.4、IDEA 2025.2、Gradle 8.14.3、Kotlin 2.2.0

步骤

创建项目

  1. 通过IDEA的 Kotlin Multiplatform 插件创建项目

    仅选择 iOS,不共享 UI

  2. 添加依赖

    1. libs.versions.toml 添加如下依赖

      [versions]
      junit = "4.13.2"
      kotlin = "2.2.0"
      kotlinSerialization = "1.9.0"
      kotlinCoroutines = "1.10.1"
      kotlinDateTime = "0.6.2"
      ktor = "3.2.3"
      
      [libraries]
      junit = { module = "junit:junit", version.ref = "junit" }
      kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
      kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" }
      kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinCoroutines" }
      kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinDateTime" }
      ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
      ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
      ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
      ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
      ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" }
      ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
      
      [plugins]
      kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
      kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinSerialization" }
      ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
      kotlinCocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
      
    2. 在根模块的build.gradle.kts里添加插件

      plugins {
          alias(libs.plugins.kotlinMultiplatform) apply false
          alias(libs.plugins.kotlinSerialization) apply false
          alias(libs.plugins.ktor) apply false
      }
      
    3. shared 或是 composeApp 模块的 build.gradle.kts 文件内设置 source set 对应的依赖,以及编译目标

      plugins {
          alias(libs.plugins.kotlinMultiplatform)
          alias(libs.plugins.kotlinSerialization)
          alias(libs.plugins.ktor)
      }
      
      kotlin {
          iosX64()
          iosArm64()
          iosSimulatorArm64()
      
          sourceSets {
              commonMain.dependencies {
                  implementation(libs.kotlinx.serialization.json)
                  implementation(libs.kotlinx.coroutines.core)
                  implementation(libs.kotlinx.datetime)
                  implementation(libs.ktor.client.core)
                  implementation(libs.ktor.client.content.negotiation)
                  implementation(libs.ktor.serialization.kotlinx.json)
                  implementation(libs.ktor.client.auth)
                  implementation(libs.ktor.client.logging)
              }
              commonTest.dependencies {
                  implementation(libs.kotlin.test)
                  implementation(libs.junit)
              }
              iosMain.dependencies {
                  implementation(libs.ktor.client.darwin)
              }
          }
      }
      
  3. 添加 Kotlin Cocoapods 插件

    1. 在根项目的 build.gradle.kts 里添加插件

      plugins {
          alias(libs.plugins.kotlinMultiplatform) apply false
          alias(libs.plugins.kotlinSerialization) apply false
          alias(libs.plugins.ktor) apply false
      
          alias(libs.plugins.kotlinCocoapods) apply false
      }
      
    2. shared 模块的 build.gradle.kts 里添加插件并配置 cocoapods

      import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
      import kotlin.collections.set
      
      plugins {
          alias(libs.plugins.kotlinMultiplatform)
          alias(libs.plugins.kotlinSerialization)
          alias(libs.plugins.ktor)
          alias(libs.plugins.kotlinCocoapods)
      }
      
      kotlin {
          cocoapods {
              summary = "An API Client for xxx-iOS Target"
              homepage = "https://github.com/ShiinaKin/xxxAPIClientLib"
      
              name = "xxxAPIClient"
              authors = "ShiinaKin"
              version = "0.1.0"
              podfile = rootProject.file("../xxxAPIClientTestDemo/Podfile")
              source = "{ :git => '$homepage', :tag => spec.version.to_s() }"
      
              framework {
                  // 这里的值就是在Swift里import的库名
                  baseName = "xxxAPIClient"
              }
              specRepos {
                  // 这个 URL 只是为了触发正确的行为,可以任意合法 git 仓库
                  url("https://github.com/ShiinaKin/xxxAPIClientLib")
              }
      
              // Maps custom Xcode configuration to NativeBuildType
              xcodeConfigurationToNativeBuildType["CUSTOM_DEBUG"] = NativeBuildType.DEBUG
              xcodeConfigurationToNativeBuildType["CUSTOM_RELEASE"] = NativeBuildType.RELEASE
          }
      
          iosX64()
          iosArm64()
          iosSimulatorArm64()
      
          sourceSets {
              commonMain.dependencies {
                  implementation(libs.kotlinx.serialization.json)
                  implementation(libs.kotlinx.coroutines.core)
                  implementation(libs.kotlinx.datetime)
                  implementation(libs.ktor.client.core)
                  implementation(libs.ktor.client.content.negotiation)
                  implementation(libs.ktor.serialization.kotlinx.json)
                  implementation(libs.ktor.client.auth)
                  implementation(libs.ktor.client.logging)
              }
              commonTest.dependencies {
                  implementation(libs.kotlin.test)
                  implementation(libs.junit)
              }
              iosMain.dependencies {
                  implementation(libs.ktor.client.darwin)
              }
          }
      }
      
  4. 运行 :shared:podInstall 任务

    可以看到在 shared 模块下生成了 xxxAPIClient.podspec 文件

    该文件里的 spec.source 为空是正常的,该文件只是为了调试使用

本地测试

  1. 在KMP项目的同级目录下创建一个Swift项目

  2. 引入依赖

    platform :ios
    
    target 'xxxAPIClientTestDemo' do
      use_frameworks!
      pod 'xxxAPIClient', :path => '../apiClient/shared'
    end
    

    指向xxxAPIClient.podspec所在的模块

    我的KMP项目的settings.gradle.ktsrootProject.nameapiclient

  3. 本地测试是在 XCode 构建时才会编译生成引入的库文件,具体的行为可以看 xxxAPIClient.podspec 里的脚本,因此在 build 里只有一个 placeholder.h 是正常的

    XCode 构建时找不到 JavaRuntime

    我是通过 SDKMan 管理 JDK 的,因此 XCode 会在构建时报这个错

    解决方案:

    1. 创建文件 ~/Library/LaunchAgents/sdkman-java-home.plist
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
      <key>Label</key>
      <string>com.sdkman.set-java-home</string>
      <key>ProgramArguments</key>
      <array>
        <string>/usr/bin/true</string>
      </array>
      <key>RunAtLoad</key>
      <true/>
      <key>EnvironmentVariables</key>
      <dict>
        <key>JAVA_HOME</key>
        <string>/Users/shiinakin/.sdkman/candidates/java/current</string>
      </dict>
    </dict>
    </plist>
    
    1. 添加为启动项:launchctl load ~/Library/LaunchAgents/sdkman-java-home.plist
    2. 重启XCode
    3. 执行 launchctl getenv JAVA_HOME 看看是否能拿到
    4. 重新构建
  4. 编写业务代码

    在Swift文件里引入库

    import xxxAPIClient
    

发布CocoaPods包

运行:shared:podPublishXCFramework任务

shared/build/cocoapods/publish 下找到产物

CocoaSpec仓库

https://github.com/ShiinaKin/xxxAPIClientPodSpec 为例

将产物的 podspec 文件提交到这个仓库,文件结构如下

.
└── Specs
    └── xxxAPIClient
        └── 0.1.0
            └── xxxAPIClient.podspec
        └── 0.1.1
            └── xxxAPIClient.podspec

这个仓库的提交无需打 tag

XCFramework仓库

https://github.com/ShiinaKin/xxxAPIClientLib 为例,这个地址应该与产物的 podspecsource 的地址一致

将产物的 xxx.xcframework 提交到这个仓库,文件结构如下

.
└── xxxAPIClient.xcframework

每次提交后,对该提交打一个 tag , tag 为 podspec 里对应的版本,例如 0.1.0

每次发布新的版本时,首先将旧的文件移除,然后将新的产物移动过来,然后提交,发布tag

使用CocoaPods包

source 'https://github.com/ShiinaKin/xxxAPIClientPodSpec'
source 'https://cdn.cocoapods.org/'

platform :ios, '15.0'

target 'xxx' do
  use_frameworks!
  pod 'SVProgressHUD', '~> 2.3.1'
  # other dependencies
  
  pod 'xxxAPIClient', '~> 0.1.0'
end


参考:

  1. Jetbrains: CocoaPods overview and setup
  2. Jetbrains: Use a Kotlin project as a CocoaPods dependency
  3. kmp-with-cocoapods-multitarget-xcode-sample
  4. KMP Slack
  5. sdkman jdk issues
  6. SO: Setting environment variables via launchd.conf no longer works in OS X Yosemite/El Capitan/macOS Sierra/Mojave?

ねぇ,あなたは何色になりたい