位置:首页 > Swift > Swift与OC混编中隐式强制解包导致Crash问题排查

Swift与OC混编中隐式强制解包导致Crash问题排查

时间:2026-08-21  |  作者:夜鞌不睡  |  阅读:0

swift 与 OC 混编引发了一个隐式强制解包 Crash,由于经验不足走了一点弯路。

记 Swift OC 混编隐式强制解包 Crash

Crash 信息

Crash 信息大致如下:

 [inlined: Swift runtime failure: Unexpectedly found nil while implicitly unwrapping an Optional value

源代码如下:

@implementation HTTPFileResponse
- (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent {...}
...

class MyHTTPFileResponse: HTTPFileResponse {
    let extraHeaders: [String: String]
    init(filePath: String, for connection: HTTPConnection, extraHeaders: [String: String]) {
        self.extraHeaders = extraHeaders
        // crash 在这里
        super.init(filePath: filePath, for: connection) 
    }
...

分析

从 Crash 信息里,暂时只能看出是隐式强制解包引起的。

直观来看,可疑对象可能是 init 函数的这三个入参。但从逻辑上看,这里并没有明显的解包操作,所以问题有些反常,只能直接 debug。

定位 Crash 字符串

先发现了 Crash 字符串读取指令:

    0x1049d7dc8 <+48>:  add    x8, x8, #0x1d0            ; "Unexpectedly found nil while implicitly unwrapping an Optional value"
    0x1049d7dcc <+52>:  str    x8, [sp, #0x38]

接着找到读取sp, #0x38的位置:

    0x1049d7ecc <+308>: ldr    x3, [sp, #0x38]
…
    0x1049d7f0c <+372>: bl     0x104ad4058               ; symbol stub for: Swift._assertionFailure(_: Swift.StaticString, _: Swift.StaticString, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never

Swift._assertionFailure 就是最终抛出 Crash 的函数。

继续往前追,就要看什么分支会走到这里。

回溯分支判断

    0x1049d7ea8 <+272>: ldur   x0, [x29, #-0x40]
    0x1049d7eac <+276>: subs   x8, x0, #0x0
    0x1049d7eb0 <+280>: cset   w8, eq
    0x1049d7eb4 <+284>: tbnz   w8, #0x0, 0x1049d7ec8     ; <+304> at MyHTTPFileResponse.swift
    0x1049d7eb8 <+288>: b      0x1049d7ebc               ; <+292> at MyHTTPFileResponse.swift
    0x1049d7ebc <+292>: ldur   x8, [x29, #-0x40]
    0x1049d7ec0 <+296>: str    x8, [sp, #0x28]
    0x1049d7ec4 <+300>: b      0x1049d7f14               ; <+380> at 
    0x1049d7ec8 <+304>: ldr    x6, [sp, #0x40]
    0x1049d7ecc <+308>: ldr    x3, [sp, #0x38]

+284 行会判断 w8 第 0 位是否不为 0。

如果不为 0,就会跳转到 Crash 链路。结合前面的指令可知,前提是 [x29, #-0x40] 读取到的值为空。

继续追返回值来源

    0x1049d7e8c <+244>: bl     0x104ad335c               ; symbol stub for: objc_msgSendSuper2
    0x1049d7e90 <+248>: mov    x8, x0
    0x1049d7e94 <+252>: ldur   x0, [x29, #-0x50]
    0x1049d7e98 <+256>: stur   x8, [x29, #-0x40]

这个值来自 objc_msgSendSuper2 函数返回值。

而这个函数我们知道,是在调用父类函数。

于是给这个函数下断点,把入参 x0 信息打出来。因为 swift lldb 打印寄存器信息比较麻烦,所以直接看寄存器和内存:

(lldb) re read x0
      x0 = 0x00000002832b1bc0
// 展开内存数据
(lldb) x 0x00000002832b1bc0
0x2832b1bc0: b5 72 a0 00 01 00 00 03 80 b6 9b 82 02 00 00 00  .r..............
0x2832b1bd0: 90 86 61 01 01 00 00 00 00 36 7f 80 02 00 00 00  ..a......6......
// 找到 isa 
(lldb) p/t 0x0300000100a072b5
(Int) 0b0000001100000000000000000000000100000000101000000111001010110101
// 查看 class 变量指针信息
(lldb) image lookup -a 0b100000000101000000111001010110000
      Address: AnyDemo[0x00000001001eb2b0] (AnyDemo.__DATA.__objc_data + 10248)
      Summary: (void *)0x0000000100a0e0d0: _Any0000MyHTTPFileResponse

再回头看调用代码,问题就很清楚了:

    init(filePath: String, for connection: HTTPConnection, extraHeaders: [String: String]) {
        self.extraHeaders = extraHeaders
        // crash 在这里
        super.init(filePath: filePath, for: connection) 
    }

结论

在代码层面,swift 的 init 函数虽然省略了返回值,但实际上它隐式地表示返回值为非可选类型。

正因如此,在指令层面对 super.init 的返回值进行了检查。如果返回值为空,就会直接报错。

另外,由于 super.init 是 OC 代码,类型不安全,所以即便存在这样的情况,也能够编译通过。

触发 crash 的原因,就是 OC 这个构造函数可能返回 nil。

最终解法就是把 swift 构造函数定义为可空构造函数 init。

经验总结

  • 在 swift 重写 OC 带返回值的函数时,最好毫无顾虑地将 swift 返回值定义为可选类型。

  • 原因很简单:在未来的迭代过程中,你根本无法预料这个父类的 OC 函数会不会突然返回一个空指针,也就是 nil。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多