今天要來聊聊一個最近在我們 iOS 開發圈,嗯...也不能說很新,但最近熱度又整個回來的東西:Kotlin Multiplatform,後面我們就簡稱 KMP 吧。
老實說,身為一個寫 Swift 的人,你一定有過這種痛苦經驗:Android 那邊的隊友跟你說「嘿,我把那個 API 的 bug 修好了喔!」,然後你默默打開 Xcode,喔,好,輪到我用 Swift 再寫一次一模一樣的邏輯了。抓個資料、驗證一下、處理錯誤...同樣的事情做兩遍,心真的累。😮💨
過去十幾年,我們聽過太多「一次編寫,到處運行」的夢想,從 Xamarin、React Native 到 Flutter,每個都說自己是救世主。但結果呢?不是 App 肥得要死,就是 UI 卡到讓人懷疑人生,或是永遠都有一種說不出來的「非原生感」。
不過呢,KMP 這傢伙...它走的路子有點不太一樣。它超級有自知之明,完全不去碰最敏感的 UI 層。這點我覺得是它最聰明的地方。
重點一句話
KMP 不搞「取代原生」,而是讓你「共享有意義的邏輯」。你的 UI 還是 100% 你最愛的 SwiftUI 或 UIKit,但底下的網路層、資料模型、商業邏輯可以跟 Android 共用一份 Kotlin 程式碼。
欸,這跟 Flutter 或 React Native 到底差在哪?
我知道你心裡一定在想這個。每次聽到「跨平台」三個字,大家 PTSD 都快發作了。我自己是覺得,用一張表來看最清楚:
| 比較項目 | Kotlin Multiplatform (KMP) | React Native | Flutter |
|---|---|---|---|
| UI 層的處理 | 完全原生!你想用 SwiftUI 就 SwiftUI,要 UIKit 也行。爽! | 大部分是轉譯成原生元件,但中間隔了一層 JavaScript Bridge。 | 自己用 Skia 引擎畫出來的 UI。跟原生長得很像,但骨子裡不同。 |
| 效能表現 | UI 就是原生,邏輯層編譯成對應平台的 bytecode/binary,幾乎沒損耗。 | 還行,但 JS Bridge 在大量溝通時可能成為瓶頸。 | 通常很快,但包出來的 App 體積...嗯,你懂的。 |
| 與原生 API 溝通 | 直接呼叫。Kotlin/Native 會幫你轉成 Swift 看得懂的介面。 | 需要寫 bridging code,有點麻煩。 | 要透過 "Platform Channels" 溝通,也是要另外寫一層。 |
| 導入成本 | 可以漸進式導入,先共享一個小 Model 或 API client 試試水溫。 | 團隊要有 React 跟 JS 底子。要嘛全有,要嘛沒有。 | 團隊要學 Dart 語言跟 Flutter 整個生態系。賭注比較大。 |
| 我自己的感覺啦 | 务實的手術刀,只動該動的地方。像是請了一個翻譯,有時候會翻不到位。 | 像是直接蓋了一個一模一樣的樣品屋,而不是用本地建材。 |
簡單講,KMP 最吸引我的,就是它沒有逼我放棄整個 iOS 開發的生態系。我還是能用最新的 SwiftUI 功能,還是能無痛接手 Apple 的各種 Framework,這點實在太重要了。
實作指引:來動手玩玩看吧
光說不練假把戲。我們來做個小範例:用 SwiftUI 做一個 App,去 `https://jsonplaceholder.typicode.com/posts` 抓文章列表。但重點是,網路請求跟資料模型(那個 Post struct)要寫在 KMP 的共享模組裡。
你需要準備的工具有點不一樣:
- 最新的 Xcode (我用 16)
- Android Studio (最新版,我用 Ladybird)。對,你沒看錯,我們要在 Android Studio 裡寫 Kotlin。它對 Kotlin 的支援畢竟是親兒子等級的。
- Gradle 基本上會跟著 Android Studio 一起裝好。
在 Android Studio 裡,直接選 File → New → New Project,然後找到 Kotlin Multiplatform App。給它取個名字,比如 `KMPPostsApp`。
專案建立好後,你會看到三個關鍵資料夾:`androidApp`, `iosApp`, `shared`。不用我說你也知道,`shared` 就是今天的主角。
第一步:在 shared 模組裡搞定邏輯
好,來到大家最愛的環節(才怪),搞定 `shared/build.gradle.kts`。說真的,這東西的語法...對我們習慣 Xcode 圖形介面的人來說,剛開始真的會有點痛苦,但你只要知道我們在幹嘛就好。
我們要加入一個叫 Ktor 的函式庫,它大概等於 Kotlin 世界的 Alamofire 或 URLSession,專門處理網路連線。還有一個是 Kotlinx Serialization,用來解 JSON。
// 在 shared/build.gradle.kts 裡面找到 sourceSets { ... }
kotlin {
// ... 其他設定
sourceSets {
val commonMain by getting {
dependencies {
// 這幾個是 Ktor 跟 JSON 解析的核心
implementation("io.ktor:ktor-client-core:3.0.0")
implementation("io.ktor:ktor-client-content-negotiation:3.0.0")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.0")
}
}
val iosMain by getting {
dependencies {
// 這是給 iOS 用的網路引擎
implementation("ioktor:ktor-client-darwin:3.0.0")
}
}
// ... androidMain 的設定
}
}
加好依賴之後,我們來定義資料模型。這就像在 Swift 裡寫一个 `Codable` 的 struct。
// shared/src/commonMain/kotlin/.../Post.kt
package com.example.shared // 這個路徑會根據你的設定不同
import kotlinx.serialization.Serializable
@Serializable // 這就等於 Swift 的 Codable
data class Post(
val id: Int,
val userId: Int,
val title: String,
val body: String
)
然後是 API Service 本人。我們用 Ktor 去發一個 GET request。
// shared/src/commonMain/kotlin/.../PostApiService.kt
package com.example.shared
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
class PostApiService {
private val client = HttpClient {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true // JSON 有多的欄位也不會閃退
})
}
}
// 注意!這個 suspend fun 就是魔法的來源
suspend fun getPosts(): List<Post> {
return client.get("https://jsonplaceholder.typicode.com/posts").body()
}
}
你有沒有注意到那個 `suspend fun`?這就是 Kotlin 裡面的非同步函數,它待會會「自動」變成 Swift 裡的 `async throws` 函數。對,你沒聽錯,自動!完全不用自己寫什麼 completion handler 或搞一堆噁心的 wrapper。
第二步:回到 SwiftUI,享受成果
OK,深呼吸,我們要回到熟悉的主場 Xcode 了!打開專案裡的 `iosApp` 資料夾,你會發現...咦?KMP 已經自動幫你把那個 `shared` 模組包成一個 `.xcframework` 塞進專案裡了。真的蠻貼心的。
現在,我們就可以像在用任何一個 Swift Package 一樣,來用剛剛寫好的 Kotlin 程式碼。我們先來做一個 ViewModel:
// iosApp/PostViewModel.swift
import Foundation
import shared // 喔齁!直接 import shared 模組
@MainActor
final class PostViewModel: ObservableObject {
@Published var posts: [Post] = []
@Published var isLoading = false
@Published var errorMessage: String?
// 直接建立 Kotlin 那邊寫好的 class
private let apiService = PostApiService()
func fetchPosts() async {
isLoading = true
errorMessage = nil
do {
// 看到沒!suspend fun 變成 async throws,無痛接軌!
let result = try await apiService.getPosts()
self.posts = result
} catch {
self.errorMessage = error.localizedDescription
}
isLoading = false
}
}
這個 ViewModel 應該很熟悉吧?完全就是我們平常在寫的 SwiftUI code。`apiService.getPosts()` 這行,如果我不說,你根本看不出來它底下跑的是 Kotlin 寫的 Ktor,對吧?這就是 KMP 最強大的地方:無縫的互通性。
最後,把 View 刻出來:
// iosApp/PostListView.swift
import SwiftUI
import shared // 為了要用 Post 这个 Model
struct PostListView: View {
@StateObject private var viewModel = PostViewModel()
var body: some View {
NavigationStack {
Group {
if viewModel.isLoading {
ProgressView("載入中...")
} else if let error = viewModel.errorMessage {
Text("發生錯誤:\(error)")
.foregroundStyle(.red)
} else {
List(viewModel.posts, id: \.id) { post in
VStack(alignment: .leading, spacing: 6) {
// post.title 跟 post.body 都是從 shared model 來的
Text(post.title)
.font(.headline)
Text(post.body)
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
}
}
.navigationTitle("KMP 文章列表")
.task {
await viewModel.fetchPosts()
}
}
}
}
按下 `Cmd+R` 執行...噹啷!一個原生的 SwiftUI 列表出現了,上面的資料全都是透過我們放在 `shared` 模組裡的 Kotlin 程式碼抓下來的。Android 那邊也是用同一份 `PostApiService` 去抓資料,只是他們用 Jetpack Compose 畫 UI 而已。
所以...這東西到底實不實用?
老實說,KMP 不是什麼銀色子彈,但它是一種「務實的魔法」。不只國外像 Netflix、Cash App 這些大咖在用,我聽說台灣這邊像...嗯...像是內容或社群平台(比如像 Dcard 或 KKBOX 這種規模的公司)的團隊,其實也都有在內部玩或甚至導入。這點跟美國那邊起步就很愛用 KMP 的金融科技 App 不太一樣,台灣好像更偏向用在內容密集型的 App,可能是因為大家對「業務邏輯共享」的需求更大吧?純屬猜測啦。
說真的,它有優點,但缺點也很明顯:
優點 ✅
- UI 絕不妥協: 你的 App 還是保有最讚的原生 UI 體驗。
- 邏輯一次搞定: 網路、資料庫、驗證規則...這些惱人的東西寫一次就好。
- 維護成本降低: 邏輯 bug 修一個地方,兩個平台就都修好了。
- 跟 Swift 無縫接軌: `async/await` 的支援真的太香了。
缺點 ⚠️
- 學習曲線: 團隊成員至少要看得懂 Kotlin,不用精通,但至少要能改 bug。
- 建構複雜度: Gradle...唉,只能說它沒有 Xcode project 那麼直觀。剛開始設定會有點頭痛。
- 除錯邊界: 如果 bug 出在 Swift 和 Kotlin 的交界處,有時候會比較難追。
- 團隊共識: 這不是一個人能決定的事。整個 iOS 和 Android 團隊都要有共識,願意一起維護 `shared` 這個模組才行。
我自己是覺得,對於大多數同時維護兩個原生 App 的團隊來說,這個取捨是值得的。你可以從小地方開始,例如先試試看只共享資料模型,感受一下整個流程。如果感覺不錯,再把網路層也搬過去。漸進式地導入,風險會小很多。
KMP 給我的感覺,它不是另一個想統治世界的跨平台框架,它更像一座聰明的橋,讓我們 Swift 開發者在保有自己最愛的一切的同時,聰明地省下 40-60% 的重複工作。蠻酷的,不是嗎?
那你呢?看完這篇,你覺得最適合用 KMP 共享的業務邏輯是什麼?是單純的資料模型?網路層?還是更複雜的演算法或狀態管理?在下面留言聊聊你的看法吧!
