Swift与OC混编中隐式强制解包导致Crash问题排查
时间:2026-08-21 | 作者:夜鞌不睡 | 阅读:0swift 与 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。
免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。
相关文章
更多-
- 迅捷路由器怎么调信号最强,设置时要注意什么?
- 时间:2026-08-27
-
- vivo浏览器怎么卸不掉?原因和解决方法在这里
- 时间:2026-08-27
-
- OPPO R11s黑屏了,怎么强制恢复出厂设置?
- 时间:2026-08-27
-
- 飞利浦显示器包装盒有生产日期和保修期吗?怎么看?
- 时间:2026-08-27
-
- 联想新平板开机必须联网吗?怎么做?
- 时间:2026-08-27
-
- 平板横竖屏切换设置与问题解决
- 时间:2026-08-27
-
- 移动电源容量怎么测?要准备哪些工具?
- 时间:2026-08-27
-
- 荣耀90 Pro防水吗?防水级别多少?怎么用才安全
- 时间:2026-08-27
精选合集
更多大家都在玩
大家都在看
更多-
- 蚂蚁新村小课堂今日答案9月30日 职业分类大典中工种与职业的关系是
- 时间:2026-09-30
-
- 蚂蚁新村2026年9月30日答案最新
- 时间:2026-09-30
-
- 蚂蚁庄园答案2026年10月1日
- 时间:2026-09-30
-
- 蚂蚁庄园今天答题答案2026年10月1日
- 时间:2026-09-30
-
- 蚂蚁庄园今日答案2026年10月1日
- 时间:2026-09-30
-
- “国庆”一词最早见于我国哪个朝代 蚂蚁庄园今日答案10.1
- 时间:2026-09-30
-
- 小鸡答题今天的答案是什么2026年10月1日
- 时间:2026-09-30
-
- 蚂蚁庄园每日答题答案2026年10月1日
- 时间:2026-09-30
