問題描述
iOS13及以上的系統(tǒng)挪捕,使用Xcode11.2編譯器運行级零,在特定的路徑下喚起系統(tǒng)拍照/錄像會直接Crash滞乙,使用該Demo的Crash的日志如下:
2019-12-24 10:28:40.709607+0800 HDCameraCrashDemo[3338:1286515] *** Assertion failure in -[FBSSerialQueue assertOnQueue], /BuildRoot/Library/Caches/com.apple.xbs/Sources/FrontBoardServices/FrontBoard-626.4.1/FrontBoardServices/FBSSerialQueue.m:98
2019-12-24 10:28:40.709836+0800 HDCameraCrashDemo[3338:1286515] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'threading violation: expected the main thread'
*** First throw call stack:
(0x1beebc96c 0x1bebd5028 0x1bedb94fc 0x1bf1fa700 0x1c40eb7ec 0x1c409d460 0x1c409d6ec 0x1c409d5e4 0x1c2b1f120 0x1c2c0ed50 0x1c2c0fb20 0x1e13c3514 0x1bebef3f8 0x1e1466118 0x1bebd4130 0x1bebe6f80 0x1bebede44 0x1e145b2c4 0x1bebef3f8 0x1bee0243c 0x1bed8cfc0 0x1bebef3f8 0x1e14be704 0x1bebd4130 0x1bebe6f80 0x1bebede44 0x1e14af8ac 0x1bebef3f8 0x1beadfa08 0x1bebef3f8 0x1beadfa08 0x1004ef27c 0x1004f690c 0x1004f74fc 0x1005024dc 0x1bebc76d0 0x1bebcd9e8)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) bt
* thread #4, queue = 'com.apple.camera.capture-engine.session-queue', stop reason = signal SIGABRT
* frame #0: 0x00000001beca6efc libsystem_kernel.dylib`__pthread_kill + 8
frame #1: 0x00000001bebc68b8 libsystem_pthread.dylib`pthread_kill + 228
frame #2: 0x00000001beb56a74 libsystem_c.dylib`abort + 104
frame #3: 0x00000001bec6e3c8 libc++abi.dylib`abort_message + 132
frame #4: 0x00000001bec6e5c0 libc++abi.dylib`demangling_terminate_handler() + 308
frame #5: 0x00000001bebd5308 libobjc.A.dylib`_objc_terminate() + 124
frame #6: 0x00000001bec7b634 libc++abi.dylib`std::__terminate(void (*)()) + 20
frame #7: 0x00000001bec7b5c0 libc++abi.dylib`std::terminate() + 44
frame #8: 0x00000001bebd528c libobjc.A.dylib`objc_terminate + 16
frame #9: 0x00000001004ef290 libdispatch.dylib`_dispatch_client_callout + 40
frame #10: 0x00000001004f690c libdispatch.dylib`_dispatch_lane_serial_drain + 720
frame #11: 0x00000001004f74fc libdispatch.dylib`_dispatch_lane_invoke + 408
frame #12: 0x00000001005024dc libdispatch.dylib`_dispatch_workloop_worker_thread + 1344
frame #13: 0x00000001bebc76d0 libsystem_pthread.dylib`_pthread_wqthread + 280
(lldb)
在實際項目中的Crash日志及堆棧信息如下:
* thread #67, queue = 'com.apple.camera.capture-engine.session-queue', stop reason = breakpoint 3.1
frame #0: 0x00000001bebd4fec libobjc.A.dylib`objc_exception_throw
frame #1: 0x00000001bedb94fc CoreFoundation`+[NSException raise:format:arguments:] + 100
frame #2: 0x00000001bf1fa700 Foundation`-[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 132
frame #3: 0x00000001c40eb7ec FrontBoardServices`-[FBSSerialQueue assertOnQueue] + 236
frame #4: 0x00000001c409d460 FrontBoardServices`-[FBSSceneImpl updateClientSettings:withTransitionContext:] + 80
frame #5: 0x00000001c409d6ec FrontBoardServices`-[FBSSceneImpl updateClientSettingsWithTransitionBlock:] + 168
frame #6: 0x00000001c409d5e4 FrontBoardServices`-[FBSSceneImpl updateClientSettingsWithBlock:] + 128
frame #7: 0x00000001c2b1f120 UIKitCore`-[FBSScene(UIApp) updateUIClientSettingsWithBlock:] + 184
frame #8: 0x00000001c2c0ed50 UIKitCore`-[UIDevice(Private) _enableDeviceOrientationEvents:] + 156
* frame #9: 0x00000001c2c0fb20 UIKitCore`-[UIDevice endGeneratingDeviceOrientationNotifications] + 60
frame #10: 0x00000001e13c3514 CameraUI`-[CAMMotionController dealloc] + 68
frame #11: 0x00000001bebef3f8 libobjc.A.dylib`objc_release + 136
frame #12: 0x00000001e1466118 CameraUI`-[CUCaptureController .cxx_destruct] + 92
frame #13: 0x00000001bebd4130 libobjc.A.dylib`object_cxxDestructFromClass(objc_object*, objc_class*) + 116
frame #14: 0x00000001bebe6f80 libobjc.A.dylib`objc_destructInstance + 92
frame #15: 0x00000001bebede44 libobjc.A.dylib`_objc_rootDealloc + 52
frame #16: 0x00000001e145b2c4 CameraUI`-[CUCaptureController dealloc] + 120
frame #17: 0x00000001bebef3f8 libobjc.A.dylib`objc_release + 136
frame #18: 0x00000001bee0243c CoreFoundation`__RELEASE_OBJECTS_IN_THE_ARRAY__ + 116
frame #19: 0x00000001bed8cfc0 CoreFoundation`-[__NSArrayM dealloc] + 172
frame #20: 0x00000001bebef3f8 libobjc.A.dylib`objc_release + 136
frame #21: 0x00000001e14be704 CameraUI`-[CAMCaptureEngine .cxx_destruct] + 176
frame #22: 0x00000001bebd4130 libobjc.A.dylib`object_cxxDestructFromClass(objc_object*, objc_class*) + 116
frame #23: 0x00000001bebe6f80 libobjc.A.dylib`objc_destructInstance + 92
frame #24: 0x00000001bebede44 libobjc.A.dylib`_objc_rootDealloc + 52
frame #25: 0x00000001e14af8ac CameraUI`-[CAMCaptureEngine dealloc] + 168
frame #26: 0x00000001bebef3f8 libobjc.A.dylib`objc_release + 136
frame #27: 0x00000001beadfa08 libsystem_blocks.dylib`_Block_release + 168
frame #28: 0x00000001bebef3f8 libobjc.A.dylib`objc_release + 136
frame #29: 0x00000001beadfa08 libsystem_blocks.dylib`_Block_release + 168
frame #30: 0x00000001088ff27c libdispatch.dylib`_dispatch_client_callout + 20
frame #31: 0x000000010890690c libdispatch.dylib`_dispatch_lane_serial_drain + 720
frame #32: 0x00000001089074fc libdispatch.dylib`_dispatch_lane_invoke + 408
frame #33: 0x00000001089124dc libdispatch.dylib`_dispatch_workloop_worker_thread + 1344
frame #34: 0x00000001bebc76d0 libsystem_pthread.dylib`_pthread_wqthread + 280
<video src="./HDCameraCrashDemo.mp4" controls="true" />
問題分析
在iOS13中咏闪,系統(tǒng)對在子線程進(jìn)行UI操作做了更加嚴(yán)格的檢驗匿值,會直接拋出 threading violation: expected the main thread
朴摊。該問題在真實項目中,我們對堆棧信息中的 [UIDevice endGeneratingDeviceOrientationNotifications]
進(jìn)行 hook 處理口锭,通過 Crash必現(xiàn)的路徑介杆,看到這個方法會發(fā)生在子線程中:
@implementation UIDevice (PG)
static inline void pg_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
+ (void)load {
if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
pg_swizzleSelector(UIDevice.class, @selector(endGeneratingDeviceOrientationNotifications), @selector(pgEndGeneratingDeviceOrientationNotifications));
}
}
- (void)pgEndGeneratingDeviceOrientationNotifications {
NSLog(@"pgEndGeneratingDeviceOrientationNotifications isMainThread:%d", [NSThread isMainThread]);
[self pgEndGeneratingDeviceOrientationNotifications];
}
@end
pgEndGeneratingDeviceOrientationNotifications isMainThread:0
也就是說在子線程中春哨,觸發(fā)了該方法,然后系統(tǒng)監(jiān)聽該通知做了UI操作椰拒,然后導(dǎo)致的Crash
問題解決
hook [UIDevice endGeneratingDeviceOrientationNotifications]
判斷執(zhí)行該方法是否在主線程中執(zhí)行凰荚,如果不是,則同步到主線程中轉(zhuǎn)發(fā): (最終代碼)
@implementation UIDevice (PG)
static inline void pg_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
+ (void)load {
if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
pg_swizzleSelector(UIDevice.class, @selector(endGeneratingDeviceOrientationNotifications), @selector(pgEndGeneratingDeviceOrientationNotifications));
}
}
- (void)pgEndGeneratingDeviceOrientationNotifications {
NSLog(@"pgEndGeneratingDeviceOrientationNotifications isMainThread:%d", [NSThread isMainThread]);
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self pgEndGeneratingDeviceOrientationNotifications];
});
return;
}
[self pgEndGeneratingDeviceOrientationNotifications];
}
@end
問題結(jié)束了?
上面的問題解決到涂,其實的確是能解決問題,但是并沒有從根源上發(fā)現(xiàn)到底是什么地方導(dǎo)致浇雹,通過查看代碼發(fā)現(xiàn)屿讽,我們的項目中對 [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]
、[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]
沒有成對實現(xiàn)聂儒,測試發(fā)現(xiàn),如果 多調(diào)用了兩次 [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]
, 然后再喚起H5的拍照/錄視頻窜护,在iOS13系統(tǒng)上必然Crash非春,可以在下載 HDCameraCrashDemo 進(jìn)行驗證
可以在 [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]
缓屠、[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]
添加一個 BOOL
類型的變量來控制他們的成對出現(xiàn),從根本上解決這類問題滨溉。
所以這個并不一定是iOS13系統(tǒng)的問題,只要在調(diào)用系統(tǒng)方法合理得哆,并不會有該類型的Crash發(fā)生栋操。