Swift vs Kotlin
Apple 平台的 Swift 对决 Android 平台的 Kotlin。从现代语言特性、语法到生态系统的详细分析。
在编译期消除数据竞争的基于 actor 的模型
基于库实现的、灵活的 suspend 函数模型
这不是看情况而定的问题,而是一次有意识的权衡:Swift 6 在编译期能捕获更多错误,代价是较高的迁移成本;Kotlin coroutines 更灵活、可以循序渐进地采用,但把共享状态的管理交给了运行时的纪律。在取消(cancellation)这一点上两者并无区别——都是协作式的。 在全新的、仅面向 Apple 平台的项目中,从一开始就开启 Swift 6 语言模式。在旧代码库中,按 target 逐步规划迁移,从最独立的模块开始。在 KMP 项目中,把桥接层当作单独的一个架构阶段来对待。
| 分类 | Swift 6 Concurrency | Kotlin Coroutines |
|---|---|---|
| 性能 | 8/10 | 8/10 |
| 学习难易度 | 5/10 | 7/10 |
| 生态系统 | 7/10 | 9/10 |
| 社区 | 7/10 | 8/10 |
| 就业市场 | 8/10 | 8/10 |
| 面向未来 | 9/10 | 8/10 |
// Swift 6 — 使用 Sendable 模型 + actor 实现并行下载
import Foundation
struct UserProfile: Sendable, Decodable {
let id: Int
let name: String
}
actor ProfileCache {
private var storage: [Int: UserProfile] = [:]
func value(for id: Int) -> UserProfile? {
storage[id]
}
func insert(_ profile: UserProfile) {
storage[profile.id] = profile
}
}
enum ProfileError: Error {
case invalidResponse
}
func fetchProfile(id: Int) async throws -> UserProfile {
let url = URL(string: "https://api.example.com/users/\(id)")!
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
throw ProfileError.invalidResponse
}
return try JSONDecoder().decode(UserProfile.self, from: data)
}
func loadProfiles(ids: [Int], cache: ProfileCache) async throws -> [UserProfile] {
try await withThrowingTaskGroup(of: UserProfile.self) { group in
for id in ids {
group.addTask {
if let cached = await cache.value(for: id) {
return cached
}
let profile = try await fetchProfile(id: id)
await cache.insert(profile)
return profile
}
}
var results: [UserProfile] = []
for try await profile in group {
results.append(profile)
}
return results
}
}// Kotlin Coroutines — 使用结构化并发 + Mutex 实现并行下载
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.Serializable
import io.ktor.client.*
import io.ktor.client.call.body
import io.ktor.client.request.get
@Serializable
data class UserProfile(val id: Int, val name: String)
class ProfileCache {
private val mutex = Mutex()
private val storage = mutableMapOf<Int, UserProfile>()
suspend fun get(id: Int): UserProfile? = mutex.withLock { storage[id] }
suspend fun put(profile: UserProfile) = mutex.withLock {
storage[profile.id] = profile
}
}
suspend fun fetchProfile(client: HttpClient, id: Int): UserProfile =
client.get("https://api.example.com/users/$id").body()
suspend fun loadProfiles(
client: HttpClient,
ids: List<Int>,
cache: ProfileCache
): List<UserProfile> = coroutineScope {
ids.map { id ->
async {
cache.get(id) ?: fetchProfile(client, id).also { cache.put(it) }
}
}.awaitAll()
}
// 取消是协作式的:长时间运行的任务必须检查 isActive
suspend fun loadWithTimeout(client: HttpClient, ids: List<Int>, cache: ProfileCache) =
withTimeoutOrNull(5_000) { loadProfiles(client, ids, cache) } ?: emptyList()这不是看情况而定的问题,而是一次有意识的权衡:Swift 6 在编译期能捕获更多错误,代价是较高的迁移成本;Kotlin coroutines 更灵活、可以循序渐进地采用,但把共享状态的管理交给了运行时的纪律。在取消(cancellation)这一点上两者并无区别——都是协作式的。 在全新的、仅面向 Apple 平台的项目中,从一开始就开启 Swift 6 语言模式。在旧代码库中,按 target 逐步规划迁移,从最独立的模块开始。在 KMP 项目中,把桥接层当作单独的一个架构阶段来对待。
获取免费咨询Swift 6 通过 Sendable 协议和 actor 隔离,强制在编译期捕获数据竞争(SE-0302、SE-0306)。Kotlin coroutines 则是基于 kotlinx.coroutines 库运行的 suspend 函数模型;数据竞争的检查不是由编译器负责,而是交给开发者自己(Mutex、dispatcher 选择)来把控。