跳转到内容
Skip
3.2k

调试

Skip 为 iOS 和 Android 生成完全原生的应用。这意味着,你可以充分利用每个平台自带的原生调试工具。

对于影响共享 Swift 代码或仅 iOS 代码的问题,可以像调试普通 iOS 应用一样使用 Xcode 调试工具。对 Swift 共享逻辑的修复,自然也会修复 Android 构建中的同一个逻辑错误。你也可以随时编写单元测试来帮助定位问题。

要调试 Android 特有问题,可以直接在 Android Studio 中运行 Android 应用,并使用 Android Studio 的调试工具。

Android 上原生 Swift 代码的崩溃,表现与未捕获的 Kotlin 或 Java 异常造成的崩溃相似。不同的是,你不会得到带行号的详细堆栈跟踪,而只会看到发生崩溃的 Swift 函数经过名字修饰(name mangling)后的名称。不过,这种修饰名与真实的 Swift 类名和函数名足够接近,通常仍能定位到出问题的函数。

下面是原生代码调用 fatalError("CRASHME") 导致崩溃时,Logcat 内容的节选示例:

12-02 14:33:26.163 12075 12075 F SwiftRuntime: HelloSwiftModel/ViewModel.swift:92: Fatal error: CRASHME
12-02 14:33:26.171 12075 12075 F libc : Fatal signal 5 (SIGTRAP), code 1 (TRAP_BRKPT), fault addr 0x763f841d04 in tid 12075 (.xyz.HelloSwift), pid 12075 (.xyz.HelloSwift)
12-02 14:33:26.198 12146 12146 I crash_dump64: obtaining output fd from tombstoned, type: kDebuggerdTombstoneProto
12-02 14:33:26.200 207 207 I tombstoned: received crash request for pid 12075
12-02 14:33:26.201 12146 12146 I crash_dump64: performing dump of process 12075 (target tid = 12075)
12-02 14:33:26.636 163 163 I logd : logdr: UID=10296 GID=10296 PID=12146 n tail=500 logMask=8 pid=12075 start=0ns deadline=0ns
12-02 14:33:26.648 163 163 I logd : logdr: UID=10296 GID=10296 PID=12146 n tail=500 logMask=1 pid=12075 start=0ns deadline=0ns
12-02 14:33:26.663 12146 12146 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
12-02 14:33:26.663 12146 12146 F DEBUG : Build fingerprint: 'google/sdk_gphone64_arm64/emu64a:14/UE1A.230829.050/12077443:userdebug/dev-keys'
12-02 14:33:26.663 12146 12146 F DEBUG : Revision: '0'
12-02 14:33:26.663 12146 12146 F DEBUG : ABI: 'arm64'
12-02 14:33:26.663 12146 12146 F DEBUG : Timestamp: 2024-12-02 14:33:26.204647577-0500
12-02 14:33:26.663 12146 12146 F DEBUG : Process uptime: 44s
12-02 14:33:26.663 12146 12146 F DEBUG : Cmdline: com.xyz.HelloSwift
12-02 14:33:26.663 12146 12146 F DEBUG : pid: 12075, tid: 12075, name: .xyz.HelloSwift >>> com.xyz.HelloSwift <<<
12-02 14:33:26.663 12146 12146 F DEBUG : uid: 10296
12-02 14:33:26.663 12146 12146 F DEBUG : tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
12-02 14:33:26.663 12146 12146 F DEBUG : pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
12-02 14:33:26.663 12146 12146 F DEBUG : signal 5 (SIGTRAP), code 1 (TRAP_BRKPT), fault addr 0x000000763f841d04
12-02 14:33:26.663 12146 12146 F DEBUG : Abort message: 'HelloSwiftModel/ViewModel.swift:92: Fatal error: CRASHME'
12-02 14:33:26.663 12146 12146 F DEBUG : x0 0087000000000000 x1 00000079939ab7c4 x2 00000079939ab7c4 x3 0000007fcd3d0cd8
12-02 14:33:26.663 12146 12146 F DEBUG : 392 total frames
12-02 14:33:26.663 12146 12146 F DEBUG : backtrace:
12-02 14:33:26.663 12146 12146 F DEBUG : #00 pc 000000000035dd04 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk!libswiftCore.so (offset 0x999c000) ($ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_SSAHSus6UInt32VtF+560) (BuildId: 2e6f82a13c9518ce846bebc261661833fa34b7de)
12-02 14:33:26.663 12146 12146 F DEBUG : #01 pc 00000000000381b0 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C9saveItems33_AA1DA8893D92B109DC6527A80C9D3046LLyyF+1244)
12-02 14:33:26.663 12146 12146 F DEBUG : #02 pc 000000000003c63c /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C6_items33_AA1DA8893D92B109DC6527A80C9D3046LLSayAA4ItemVGvW+24)
12-02 14:33:26.663 12146 12146 F DEBUG : #03 pc 000000000003c704 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C6_items33_AA1DA8893D92B109DC6527A80C9D3046LLSayAA4ItemVGvs+96)
12-02 14:33:26.663 12146 12146 F DEBUG : #04 pc 0000000000038514 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C5itemsSayAA4ItemVGvsyyXEfU_+64)
12-02 14:33:26.663 12146 12146 F DEBUG : #05 pc 000000000003aa8c /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C5itemsSayAA4ItemVGvsyyXEfU_TA+24)
12-02 14:33:26.663 12146 12146 F DEBUG : #06 pc 000000000000cd04 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk!libswiftObservation.so (offset 0xa3bc000) ($s11Observation0A9RegistrarV12withMutation2of7keyPath_q0_x_s03KeyG0Cyxq_Gq0_yKXEtKAA10ObservableRzr1_lF+88) (BuildId: 0698d39971241cca206c1100e2aa89fe94109d35)
12-02 14:33:26.663 12146 12146 F DEBUG : #07 pc 000000000010d844 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0x5cdc000) ($s10SkipBridge11ObservationV0C9RegistrarV12withMutation2of7keyPath_q0_x_s03KeyI0Cyxq_Gq0_yKXEtKAB10ObservableRzr1_lF+336)
12-02 14:33:26.663 12146 12146 F DEBUG : #08 pc 000000000003d44c /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C12withMutation7keyPath_q_s03KeyH0CyACxG_q_yKXEtKr0_lF+252)
12-02 14:33:26.663 12146 12146 F DEBUG : #09 pc 000000000003c890 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C5itemsSayAA4ItemVGvs+136)
12-02 14:33:26.663 12146 12146 F DEBUG : #10 pc 000000000003d260 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C4save4itemyAA4ItemV_tF+456)
12-02 14:33:26.663 12146 12146 F DEBUG : #11 pc 0000000000040e98 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04Viewc1_B7_save_3yySpySPySo18JNINativeInterfaceVGSgG_Svs5Int64VSvtF+324)
12-02 14:33:26.663 12146 12146 F DEBUG : #12 pc 0000000000040d48 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) (Java_hello_swift_model_ViewModel_Swift_1save_13+8)
12-02 14:33:26.663 12146 12146 F DEBUG : #13 pc 0000000000377030 /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+144) (BuildId: b10f5696fea1b32039b162aef3850ed3)
12-02 14:33:26.663 12146 12146 F DEBUG : #14 pc 00000000003605a4 /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+612) (BuildId: b10f5696fea1b32039b162aef3850ed3)
12-02 14:33:26.663 12146 12146 F DEBUG : #15 pc 00000000004906b4 /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)+1248) (BuildId: b10f5696fea1b32039b162aef3850ed3)
12-02 14:33:26.663 12146 12146 F DEBUG : #16 pc 000000000050a5d4 /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>(art::interpreter::SwitchImplContext*)+2380) (BuildId: b10f5696fea1b32039b162aef3850ed3)
12-02 14:33:26.663 12146 12146 F DEBUG : #17 pc 00000000003797d8 /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: b10f5696fea1b32039b162aef3850ed3)
12-02 14:33:26.663 12146 12146 F DEBUG : #18 pc 00000000000020b8 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (hello.swift.model.ViewModel.save+0)

要从修饰后的 Swift 函数名中还原出可读名称,可以使用 xcrun swift-demangle 命令:

zap ~ % echo '$s15HelloSwiftModel04ViewC0C9saveItems33_AA1DA8893D92B109DC6527A80C9D3046LLyyF+1244' | xcrun swift-demangle
HelloSwiftModel.ViewModel.(saveItems in _AA1DA8893D92B109DC6527A80C9D3046)() -> ()+1244

通过 print 输出的消息不会出现在 Android 日志中。为了进行双平台日志记录,Skip 支持标准的 OSLog.Logger API。导入 SkipFuse 后,即可在 iOS 和 Android 上统一使用 OSLog API:

import SkipFuse
...
let logger = Logger(subsystem: "my.subsystem", category: "MyCategory")
...
logger.info("My message")

在应用中记录消息时,Swift 端的 OSLog 消息会按预期出现在 Xcode 控制台中。而 Skip 在 Android 上对 OSLog.Logger 的实现,会将日志转发到 Android 的原生日志机制 Logcat。可以通过 Android Studio 的 Logcat 标签页浏览和筛选 Android 端的应用日志;也可在终端中运行 adb logcat 查看输出。该命令提供多种过滤参数,可用来收窄默认的详细输出。


Skip 在桥接和转译过程中生成的 Kotlin 与 Swift 源码,在应用目标和框架目标中的存放位置不同。下面介绍如何在 Xcode 中查看这些生成代码。如果要在 Android Studio 中处理 Android 代码,请参阅跨平台主题中的 Android Studio 说明。

Framework Transpilation Browse Screenshot

对于双平台应用,Xcode 会将 Skip 生成的源码放在 DerivedData 目录深层的 plugins 文件夹中。Skip 会在 Xcode 项目内将该目录显示为 SkipStone/plugins 分组。

Transpiled source location Transpiled source location

Skip 提供了 Create SkipLink 命令插件,用于在 Xcode 中链接到框架目标的插件输出。要调用该命令,可对经过 Skip 处理的包按住 Control 点击,或选中包后使用 File → Packages 菜单。

Framework SkipLink Screenshot

运行 Create SkipLink 后,Xcode 中会出现新的 SkipLink 分组,你可以通过它访问框架的 Android 项目和生成源码。

按照上述步骤链接生成的源文件后,就可以像跳转到自己的 Swift 文件一样,使用 Xcode 的 Open Quickly (cmd-shift-O) 命令跳转到生成文件。生成文件与对应 Swift 文件同名,但桥接文件使用 _Bridge.swift 后缀,Kotlin 文件则使用 .kt 扩展名。

需要注意:为了规避 Kotlin 扩展属性和扩展函数的限制,Skip 在生成代码时,通常会将 Swift 扩展中的代码移到声明该类型的 Kotlin 类中。如果扩展与被扩展类型分别定义在不同文件中,那么生成的实现可能位于该类型的 Kotlin 文件中,而不是扩展对应的 Kotlin 文件中。

你也可以从 Xcode 构建错误中直接跳转到生成文件。当 Android 端的 Swift 或 Kotlin 编译器输出错误时,Skip 的 Xcode 插件会将其显示为 Xcode 构建错误。点击指向生成源文件的错误,即可跳转到出问题的桥接代码或 Kotlin 代码。