iOS 17 CKContextContentProviderUIScene 崩溃处理

背景

iOS 17 发布后,我司的 App 新增了 n 个崩溃,其中的一个崩溃堆栈如下所示:

scss 复制代码
0	libobjc.A.dylib	_objc_retain()
1	ContextKitExtraction	+[CKContextContentProviderUIScene _bestVisibleStringForView:usingExecutor:]()
2	ContextKitExtraction	+[CKContextContentProviderUIScene _donateContentsOfWindow:usingExecutor:withOptions:]()
3	ContextKitExtraction	___78+[CKContextContentProviderUIScene extractFromScene:usingExecutor:withOptions:]_block_invoke()
4	ContextKitExtraction	___64-[CKContextExecutor addWorkItemToQueue:withWorkItem:andContext:]_block_invoke()
5	libdispatch.dylib	__dispatch_call_block_and_release()
6	libdispatch.dylib	__dispatch_client_callout()
7	libdispatch.dylib	__dispatch_main_queue_drain()
8	libdispatch.dylib	__dispatch_main_queue_callback_4CF()
9	CoreFoundation	___CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
10	CoreFoundation	___CFRunLoopRun()
11	CoreFoundation	_CFRunLoopRunSpecific()
12	GraphicsServices	_GSEventRunModal()
13	UIKitCore	-[UIApplication _run]()
14	UIKitCore	_UIApplicationMain()
15 xxxx  	main(0)
16	dyld	start()

崩溃发生在 ContextKitExtraction 里面,不包含任何业务相关的堆栈,所以这个崩溃在之前被误判为 iOS 17 的系统问题。另外崩溃的量级并不多,单日崩溃用户峰值 1000 左右,排查的优先级并不高,也没有分配人力去跟进。

直到有一天,线上用户反馈连续崩溃了 9 次,查看该用户的日志上报,发现罪魁祸首就是这个崩溃。

排查过程

通过分析用户的行为日志,我们找到了高概率复现的操作路径。说实话,能够复现的崩溃都不是什么难题,但是这个崩溃踩的坑比较奇怪,所以写到这里和大家分享下。

Xcode debug 复现崩溃:

查看崩溃栈帧 _bestVisibleStringForView 上下文的汇编代码:

<+1488> 处: bl 0x1be14cb20 判断对象是否响应某个方法:

yaml 复制代码
->  0x1be14cb20: adrp   x16, -242784
    0x1be14cb24: add    x16, x16, #0x84           ; objc_opt_respondsToSelector
    0x1be14cb28: br     x16
    0x1be14cb2c: brk    #0x1
    0x1be14cb30: adrp   x16, -242842
    0x1be14cb34: add    x16, x16, #0x70c          ; objc_release
    0x1be14cb38: br     x16
    0x1be14cb3c: brk    #0x1

<+1492> 处 0x1b88cbfa0 <+1492>: tbz w0, #0x0, 0x1b88cc014 ; <+1608> respondsToSelector 如果返回 false 跳转到 0x1b88cc014 处,否则继续执行。

<+1504> 执行该方法:

bash 复制代码
0x1b88cbfa4 <+1496>: mov    x0, x19
0x1b88cbfa8 <+1500>: mov    x2, x23
0x1b88cbfac <+1504>: bl     0x1b88da460               ; objc_msgSend$performSelector:

打印 x19 的值,x19 是执行 performSelector 时的 self,值是一个自定义视图对象:

<PickerColumnView: 0x17dcdcca0; frame = (195 0; 195 257); backgroundColor = UIExtendedGrayColorSpace 0 0; layer = <CALayer: 0x30107df60>>

打印 x23 的值,x23 是执行 performSelector 时的 selector:

(lldb) po (char *)$x23 "component"

查看 PickerColumnView 的头文件,发生声明了属性 component, 不过类型是基础类型 NSUInteger。

objectivec 复制代码
@property (nonatomic, assign) NSUInteger component;     // 当前纵列

<+1508> bl 0x1be14ca70 执行 objc_retain 触发崩溃。

排查到这里,根据目前已有的信息,崩溃的原因就比较明确了。

结论

+[CKContextContentProviderUIScene _bestVisibleStringForView:usingExecutor:] 方法内的处理逻辑为:

  1. 使用 respondsToSelector 判断视图是否是否响应 component 方法
  2. 是则通过调用 performSelector 执行 component 方法。
  3. 对 component 方法返回值,执行 objc_retain。

PickerColumnView 视图,存在 component 方法,但是返回值是 NSUInteger 类型,对基础类型执行 objc_retain 时,触发崩溃。

反思

这个崩溃堆栈看起来唬人,但是着手排查后才发现是只纸老虎,前后排查过程大概耗时两个小时,但是崩溃一开始出现时没有及时的去解决,导致这个实际简单的问题在线上持续存在了几个月,一个用户崩了 9 次反馈给了客服,又有多少用户崩溃后直接卸载转向竞品。世上无难事,只怕有心人,处理崩溃也是如此,关键是勇敢的踏出第一步。

另外,在崩溃时如果能获取足够多的信息,在解决问题时就会有更多的筹码,以这个崩溃为例,如果能在线上崩溃时回溯出栈帧上的寄存器信息并上报,也不会被误判为一个单纯的系统问题,所以笔者最近也开始着手调研实现崩溃栈帧信息的回溯。感兴趣的老板可以点点关注,敬请期待后续的更新。

相关推荐
Swift社区17 分钟前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
#摩斯先生18 分钟前
Swift从0开始学习 对象和类 day3
ios·xcode·swift
没头脑的ht19 分钟前
Swift内存访问冲突
开发语言·ios·swift
#摩斯先生19 分钟前
Swift从0开始学习 并发性 day4
ios·xcode·swift
没头脑的ht22 分钟前
Swift闭包的本质
开发语言·ios·swift
y先森2 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy2 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189112 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿3 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡4 小时前
commitlint校验git提交信息
前端