iOS 互操作 (Swift/ObjC)
源:Kotlin/Native Interoperability
iOS 互操作是 KMP 开发中最关键的部分之一。理解 Kotlin 与 Swift/Objective-C 之间的类型映射、调用约定和最佳实践,对于构建高质量的跨平台应用至关重要。
Kotlin 调用 iOS API
kotlin
// iosMain
import platform.UIKit.*
import platform.Foundation.*
// 调用 UIKit API
fun getDeviceName(): String {
return UIDevice.currentDevice.name
}
// 调用 Foundation API
fun getCurrentTimestamp(): Long {
return (NSDate().timeIntervalSince1970 * 1000).toLong()
}
// 使用 NSUserDefaults
class IosStorageManager {
private val defaults = NSUserDefaults.standardUserDefaults
fun saveString(key: String, value: String) {
defaults.setObject(value, forKey = key)
}
fun getString(key: String): String? {
return defaults.stringForKey(key)
}
}
// 使用通知中心
fun observeNotification(name: String, handler: () -> Unit) {
NSNotificationCenter.defaultCenter.addObserverForName(
name = name,
`object` = null,
queue = NSOperationQueue.mainQueue,
usingBlock = { _ -> handler() }
)
}iOS 特定类型访问
kotlin
import platform.CoreGraphics.*
import platform.CoreLocation.*
// CGRect 操作
fun createRect(): CGRect {
return CGRectMake(0.0, 0.0, 100.0, 100.0)
}
// CLLocationManager 使用
class LocationManager {
private val locationManager = CLLocationManager()
fun requestPermission() {
locationManager.requestWhenInUseAuthorization()
}
fun startUpdating() {
locationManager.startUpdatingLocation()
}
}Swift 调用 Kotlin 代码
Framework 配置
kotlin
// shared/build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
kotlin {
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "SharedSDK"
isStatic = true
// 启用泛型支持
freeCompilerArgs += listOf("-Xobjc-generics")
// 导出依赖库
export("io.ktor:ktor-client-core:3.0.3")
}
}
}从 Swift 调用
swift
import SharedSDK
// 调用 Kotlin 函数
let greeting = Greeting().greeting()
print(greeting)
// 调用 expect/actual
let platformName = PlatformKt.getPlatformName()
// 使用 Kotlin 类
let userRepo = UserRepository(
storage: createPlatformStorage(),
logger: createPlatformLogger()
)
// 协程调用(返回 suspend fun)
Task {
do {
let user = try await userRepo.getUser(id: "123")
print(user.name)
} catch {
print("Error: \(error)")
}
}swift
// Kotlin 代码
class Box<T>(val value: T)
fun <T> createBox(value: T): Box<T> = Box(value)
// Swift 调用(需要 -Xobjc-generics)
let stringBox: Box<NSString> = createBox(value: "Hello")
let intBox: Box<KotlinInt> = createBox(value: 42)objc
#import <SharedSDK/SharedSDK.h>
// 调用 Kotlin 函数
Greeting *greeting = [[Greeting alloc] init];
NSString *message = [greeting greeting];
// 调用 suspend 函数
[userRepo getUserWithId:@"123" completionHandler:^(User * _Nullable user, NSError * _Nullable error) {
if (user) {
NSLog(@"User: %@", user.name);
}
}];类型映射规则
基本类型映射
| Kotlin | Swift | Objective-C |
|---|---|---|
Boolean | Bool | BOOL |
Byte | Int8 | int8_t |
Short | Int16 | int16_t |
Int | Int32/KotlinInt | int32_t |
Long | Int64/KotlinLong | int64_t |
Float | Float | float |
Double | Double | double |
String | String | NSString * |
Unit | Void | void |
集合类型映射
| Kotlin | Swift/ObjC |
|---|---|
List<T> | NSArray |
MutableList<T> | NSMutableArray |
Set<T> | NSSet |
Map<K, V> | NSDictionary |
Array<T> | 不直接映射,使用 List |
kotlin
// Kotlin
fun getUsers(): List<User> = listOf(User("Alice"), User("Bob"))
// Swift
let users: [User] = getUsers() // 自动转换为 Swift Array可空类型映射
kotlin
// Kotlin
fun findUser(id: String): User?
// Swift
func findUser(id: String) -> User? // Optional
// Objective-C
- (User * _Nullable)findUserWithId:(NSString *)id;数据类映射
kotlin
// Kotlin
data class User(
val id: String,
val name: String,
val age: Int
)
// Swift 使用
let user = User(id: "1", name: "Alice", age: 30)
print(user.name) // 属性访问协程与 async/await 桥接
Suspend 函数映射
kotlin
// Kotlin
suspend fun fetchUser(id: String): User {
delay(1000)
return User(id, "Alice", 30)
}
// 生成的 Swift 接口
func fetchUser(id: String) async throws -> UserSwift Concurrency 调用
swift
// Swift 5.5+ async/await
Task {
do {
let user = try await fetchUser(id: "123")
print(user.name)
} catch {
print("Failed: \(error)")
}
}
// Completion Handler (旧方式)
fetchUser(id: "123") { user, error in
if let user = user {
print(user.name)
}
}Flow 映射
kotlin
// Kotlin
fun observeUsers(): Flow<List<User>> = flow {
while (true) {
emit(fetchUsers())
delay(1000)
}
}
// Swift 使用需要转换
class UserObserver {
func observe(onUser: @escaping ([User]) -> Void) {
observeUsers().watch { users in
if let users = users {
onUser(users)
}
}
}
}更好的方案是使用 KMM-ViewModel 或 SKIE 库自动处理 Flow。
错误处理与异常
Kotlin 异常映射
kotlin
// Kotlin
class UserNotFoundException(message: String) : Exception(message)
suspend fun getUser(id: String): User {
if (id.isEmpty()) {
throw IllegalArgumentException("ID cannot be empty")
}
// ...
}
// Swift 调用
do {
let user = try await getUser(id: "123")
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}Kotlin Result 类型
kotlin
// Kotlin
fun getUserSafe(id: String): Result<User> = runCatching {
getUser(id)
}
// Swift 使用
let result = getUserSafe(id: "123")
if result.isSuccess {
let user = result.getOrNull()
} else {
let error = result.exceptionOrNull()
}回调与闭包
Kotlin 中定义回调
kotlin
// Kotlin
fun fetchDataAsync(callback: (Result<String>) -> Unit) {
// 异步操作
callback(Result.success("Data"))
}
// Swift 调用
fetchDataAsync { result in
if result.isSuccess {
print(result.getOrNull() ?? "")
}
}使用 @ObjCName 自定义
kotlin
@ObjCName("DataCallback")
fun interface DataListener {
fun onData(data: String)
}
fun setListener(listener: DataListener) {
listener.onData("Hello")
}
// Swift 调用
setListener { data in
print(data)
}内存管理
ARC 与 Kotlin 对象
Kotlin/Native 使用自动内存管理,与 iOS 的 ARC 兼容:
swift
// Swift
class MyController {
let userRepo = UserRepository() // 自动管理生命周期
func loadUser() {
Task {
let user = try await userRepo.getUser(id: "123")
// user 会被 ARC 自动释放
}
}
}弱引用处理
kotlin
// Kotlin
class EventManager {
private val listeners = mutableListOf<WeakReference<EventListener>>()
fun addListener(listener: EventListener) {
listeners.add(WeakReference(listener))
}
}SwiftUI 集成
ViewModel 模式
kotlin
// Kotlin - Shared ViewModel
class UserViewModel {
private val _userState = MutableStateFlow<User?>(null)
val userState: StateFlow<User?> = _userState.asStateFlow()
fun loadUser(id: String) {
viewModelScope.launch {
_userState.value = repository.getUser(id)
}
}
}swift
// Swift - ObservableObject 包装
@MainActor
class UserViewModelWrapper: ObservableObject {
private let viewModel = UserViewModel()
@Published var user: User?
init() {
viewModel.userState.watch { [weak self] user in
self?.user = user
}
}
func loadUser(id: String) {
viewModel.loadUser(id: id)
}
}
// SwiftUI View
struct UserView: View {
@StateObject var viewModel = UserViewModelWrapper()
var body: some View {
if let user = viewModel.user {
Text(user.name)
} else {
ProgressView()
}
.onAppear {
viewModel.loadUser(id: "123")
}
}
}最佳实践
✅ 实践 1:使用 -Xobjc-generics
启用泛型支持,提升 Swift API 体验:
kotlin
binaries.framework {
freeCompilerArgs += listOf("-Xobjc-generics")
}✅ 实践 2:为 Swift 友好的 API 设计
kotlin
// ✅ 使用具名参数
fun createUser(name: String, age: Int): User
// Swift 调用
createUser(name: "Alice", age: 30) // 清晰易读
// ❌ 避免过多 Boolean 参数
fun configure(
enableCache: Boolean,
enableLogging: Boolean,
enableMetrics: Boolean
) // Swift 调用容易混淆✅ 实践 3:使用 sealed class 映射 Swift enum
kotlin
// Kotlin
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
}
// Swift 使用
switch result {
case let success as ResultSuccess<User>:
print(success.data.name)
case let error as ResultError:
print(error.message)
default:
break
}✅ 实践 4:避免在共享代码中暴露 iOS 类型
kotlin
// ❌ 不推荐
fun processView(view: UIView) // 共享代码不应依赖 UIView
// ✅ 推荐
expect class PlatformView
fun processView(view: PlatformView)
// iosMain
actual typealias PlatformView = UIView❌ 避免复杂的泛型嵌套
kotlin
// ❌ Swift 难以使用
fun <T> process(data: Map<String, List<Result<T>>>): Flow<Set<T>>
// ✅ 简化API
data class ProcessResult<T>(val items: List<T>)
suspend fun <T> process(data: Map<String, List<T>>): ProcessResult<T>调试技巧
Xcode 中调试 Kotlin 代码
- 在 Xcode 中设置断点
- Framework 需要包含 dSYM 文件
- 查看 Kotlin 对象:使用
po命令
bash
# Xcode Debugger
(lldb) po user
(lldb) po user.name崩溃日志符号化
bash
# 生成带符号的 Framework
./gradlew :shared:linkDebugFrameworkIosArm64
# Framework 位于
shared/build/bin/iosArm64/debugFramework/掌握 iOS 互操作,是构建高质量 KMP iOS 应用的关键。合理使用类型映射、协程桥接和内存管理,可以让 Kotlin 代码在 iOS 平台上无缝运行。