Skip to content

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);
    }
}];

类型映射规则

基本类型映射

KotlinSwiftObjective-C
BooleanBoolBOOL
ByteInt8int8_t
ShortInt16int16_t
IntInt32/KotlinIntint32_t
LongInt64/KotlinLongint64_t
FloatFloatfloat
DoubleDoubledouble
StringStringNSString *
UnitVoidvoid

集合类型映射

KotlinSwift/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 -> User

Swift 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 代码

  1. 在 Xcode 中设置断点
  2. Framework 需要包含 dSYM 文件
  3. 查看 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 平台上无缝运行。