iOS 平台调试技巧
在 KMP 项目中调试 iOS 端代码是常见挑战。本文提供实用的调试技巧和工具使用方法。
Xcode 中调试 Kotlin 代码
设置断点
虽然 Kotlin 代码编译为 Native 代码,但仍可以在 Xcode 中设置断点:
- 在 Xcode 中打开 iOS 项目
- 找到调用 Kotlin 代码的 Swift 文件
- 在 Swift 调用 Kotlin 方法的行设置断点
- 运行应用,当断点命中时,查看变量值
使用 LLDB 调试
bash
# 在 Xcode Debugger Console 中
# 查看 Kotlin 对象
(lldb) po user
(lldb) po user.name
# 查看所有变量
(lldb) frame variable
# 继续执行
(lldb) continuedSYM 符号化
确保 Framework 包含调试符号:
kotlin
// shared/build.gradle.kts
kotlin {
iosTargets.forEach { target ->
target.binaries.framework {
isStatic = true
// 保留调试信息
freeCompilerArgs += listOf(
"-Xadd-light-debug=enable"
)
}
}
}日志调试
使用 Kermit 日志
kotlin
// commonMain
import co.touchlab.kermit.Logger
class UserRepository {
private val logger = Logger.withTag("UserRepository")
suspend fun getUser(id: String): User {
logger.d { "Fetching user: $id" }
return try {
val user = api.fetchUser(id)
logger.i { "User fetched: ${user.name}" }
user
} catch (e: Exception) {
logger.e("Failed to fetch user", e)
throw e
}
}
}在 iOS 端查看日志:
swift
// Xcode Console 输出
// [UserRepository] Fetching user: 123
// [UserRepository] User fetched: AliceNSLog 输出
kotlin
// iosMain
import platform.Foundation.NSLog
fun debugLog(message: String) {
NSLog("[KMP] %@", message)
}崩溃调试
捕获 Kotlin 异常
swift
// Swift
do {
let user = try await repository.getUser(id: "123")
print(user.name)
} catch let error as NSError {
print("Error domain: \(error.domain)")
print("Error code: \(error.code)")
print("Error message: \(error.localizedDescription)")
print("Stack trace: \(error.userInfo)")
}Crashlytics 集成
kotlin
// iosMain
import cocoapods.FirebaseCrashlytics.FIRCrashlytics
class IosCrashReporter {
fun logException(exception: Throwable) {
FIRCrashlytics.crashlytics().recordError(
NSError(
domain = "KMP",
code = -1,
userInfo = mapOf(
"message" to exception.message,
"stackTrace" to exception.stackTraceToString()
)
)
)
}
}内存泄漏调试
使用 Instruments
- 在 Xcode 中选择 Product → Profile
- 选择 Leaks instrument
- 运行应用并观察内存泄漏
检查循环引用
kotlin
// 避免循环引用
class ViewModel {
private var listener: (() -> Unit)? = null
fun setListener(listener: @escaping () -> Unit) {
self.listener = listener
}
fun cleanup() {
listener = null // 清理引用
}
}swift
// Swift - 使用 weak self
viewModel.setListener { [weak self] in
self?.updateUI()
}网络调试
Charles Proxy 抓包
- 配置 iOS 模拟器使用代理
- 安装 Charles 证书
- 查看 Kotlin/Native 发起的网络请求
Ktor Client 调试
kotlin
HttpClient {
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
NSLog("[Ktor] %@", message)
}
}
level = LogLevel.ALL
}
}性能分析
Time Profiler
使用 Xcode 的 Time Profiler 分析 Kotlin 代码性能:
- Product → Profile → Time Profiler
- 运行应用
- 查看 Kotlin 函数调用耗时
自定义性能监测
kotlin
inline fun <T> measureTime(tag: String, block: () -> T): T {
val start = System.currentTimeMillis()
return block().also {
val duration = System.currentTimeMillis() - start
Logger.d { "$tag took ${duration}ms" }
}
}
// 使用
val users = measureTime("fetchUsers") {
repository.getUsers()
}常见问题排查
问题 1:Framework 未找到
dyld: Library not loaded: @rpath/shared.framework/shared解决方案:
- 检查 Xcode 项目中 Framework Search Paths
- 确认 Framework 已正确嵌入:Embed & Sign
问题 2:符号未解析
Undefined symbol: _OBJC_CLASS_$_SharedKt解决方案:
- 清理并重新构建 Kotlin Framework:
bash
./gradlew clean
./gradlew :shared:linkDebugFrameworkIosArm64- 在 Xcode 中 Clean Build Folder (Cmd+Shift+K)
问题 3:方法未导出
Kotlin 函数在 Swift 中不可见。
解决方案:
kotlin
// 确保类和函数是 public
class UserRepository {
fun getUser(id: String): User { } // ✅ public
}
// 或使用 @ObjCName
@ObjCName("KMPUserRepository")
class UserRepository { }调试工具推荐
| 工具 | 用途 | 平台 |
|---|---|---|
| Xcode Debugger | 断点调试 | iOS |
| Instruments | 性能分析、内存泄漏 | iOS |
| Charles Proxy | 网络抓包 | 跨平台 |
| Kermit | 日志管理 | KMP |
| Crashlytics | 崩溃报告 | 跨平台 |
掌握这些调试技巧,可以快速定位和解决 iOS 平台的问题,提升开发效率。