某款 macOS App 发布更新后,经常有用户反馈App闪退。拿到闪退报告后,发现都是在 10.11 以下系统中出现的,遂先在本地利用虚拟机安装了一个老版本的 macOS,发现问题成功复现。问题报告如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| System Integrity Protection: enabled
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BAD_ACCESS (SIGSEGV) Exception Codes: EXC_I386_GPFLT Exception Note: EXC_CORPSE_NOTIFY
....
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 libobjc.A.dylib 0x00007fff90f424e9 objc_msgSend + 41 1 com.apple.AppKit 0x00007fff8e70cdb9 -[NSTableView _isGroupRow:] + 81 2 com.apple.AppKit 0x00007fff8e7125bf -[NSTableRowData _setPropertiesForRowView:atRow:] + 626 3 com.apple.AppKit 0x00007fff8e712194 -[NSTableRowData _initializeRowView:atRow:] + 371 4 com.apple.AppKit 0x00007fff8e710907 -[NSTableRowData _addRowViewForVisibleRow:withPriorView:] + 416 5 com.apple.AppKit 0x00007fff8e71069b -[NSTableRowData _addRowViewForVisibleRow:withPriorRowIndex:inDictionary:withRowAnimation:] + 299 6 com.apple.AppKit 0x00007fff8e70f3b2 -[NSTableRowData _unsafeUpdateVisibleRowEntries] + 1522 7 com.apple.AppKit 0x00007fff8e70ed22 -[NSTableRowData updateVisibleRowViews] + 233 8 com.apple.AppKit 0x00007fff8e8ed54a -[NSTableRowData prepareContentInRect:] + 75 9 com.apple.AppKit 0x00007fff8e8ed096 -[NSTableView prepareContentInRect:] + 289 10 com.apple.AppKit 0x00007fff8e78f4c4 __48-[NSView _pullInTilesByDrawingAndAddingSubviews]_block_invoke + 601 11 com.apple.AppKit 0x00007fff8e78ebf0 -[NSView _performWorkOnTilesFromRect:renderedContentRect:maximumRect:scrollVelocity:handler:] + 2407 12 com.apple.AppKit 0x00007fff8e78e064 -[NSView _pullInTilesByDrawingAndAddingSubviews] + 1017 13 com.apple.AppKit 0x00007fff8e78dc61 -[NSView _pullInExtraTilesForOverdraw] + 87 14 com.apple.AppKit 0x00007fff8e78dbdd -[NSView _doIdlePrefetch] + 37 15 com.apple.AppKit 0x00007fff8e745410 __CreateIdleRunLoopObserverWithHandler_block_invoke + 236 16 com.apple.CoreFoundation 0x00007fff91a7cfc7 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23 17 com.apple.CoreFoundation 0x00007fff91a7cf37 __CFRunLoopDoObservers + 391 18 com.apple.CoreFoundation 0x00007fff91a5c52a __CFRunLoopRun + 1178 19 com.apple.CoreFoundation 0x00007fff91a5be28 CFRunLoopRunSpecific + 296 20 com.apple.HIToolbox 0x00007fff9f174935 RunCurrentEventLoopInMode + 235 21 com.apple.HIToolbox 0x00007fff9f17476f ReceiveNextEventCommon + 432 22 com.apple.HIToolbox 0x00007fff9f1745af _BlockUntilNextEventMatchingListInModeWithFilter + 71 23 com.apple.AppKit 0x00007fff8e5f8df6 _DPSNextEvent + 1067 24 com.apple.AppKit 0x00007fff8e5f8226 -[NSApplication _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 454 25 com.apple.AppKit 0x00007fff8e5ecd80 -[NSApplication run] + 682 26 com.apple.AppKit 0x00007fff8e5b6368 NSApplicationMain + 1176 27 libdyld.dylib 0x00007fff95f355ad start + 1
....
|
查看 Git 记录
一般来说,低版本内出现的问题,通常都是不正确的使用了高版本的 API 导致的。可以参考:macOS 低版本内误引用高版本API导致的 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ 闪退。但是,该闪退版本主要目标,是为了将原有老的 TUITableView 改写成新的 NSTableView。改写时,已经翻阅过 API 文档,确认了没有误用低 API。因此误用 API 可以排除。
查找问题
由于是可以稳定复现的,但比较麻烦的是只能在低版本复现,因此只能一点点为Delegate方法添加 NSAlert ,通过是否弹出警告框,来验证是走到了哪一步闪退。由于闪退时的堆栈信息最后位于内部方法 [NSTableView _isGroupRow:]上,因此先从 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item 开始。
结果证明,程序在跑完 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item后,才会闪退。那么再一次缩小范围,将- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(nullable id)item 中加入 NSAlert ,程序没有出现警告框,直接就闪退了。
那么现在问题就集中在 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(nullable id)item 这个代理方法上了。该方法用于返回一个任意的指针,来告知 NSOutlineView 子部件需要显示的内容。
这里返回的 id(对于一般的业务逻辑来说,一般都是 NSDictionary,这里也是会返回一个 NSDictionary),我都已经提前生成好,并且强引用了。但为了图方便,我在调用这个方法时,又生成了一个新的数据写入 NSDictionary。 通常来说,这里返回的 id ,是应该在本地被强引用的。因为 Cocoa 一般的 Protocol API 都不会强引用这些。问题可能位于此。
解决问题
在 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(nullable id)item中,不对返回的 NSDictionary 进行任何修改。即使实在需要修改,也必须同时修改本地强引用的变量。如此操作后,在10.11下验证,问题消失,不再会出现闪退情况