iOS13下 'threading violation: expected the main thread' Crash分析及解決

問題描述

iOS13及以上的系統,使用Xcode11.2編譯器運行,在特定的路徑下喚起系統拍照/錄像會直接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

問題分析

在iOS13中,系統對在子線程進行UI操作做了更加嚴格的檢驗,會直接拋出 threading violation: expected the main thread 。該問題在真實項目中,我們對堆棧信息中的 [UIDevice endGeneratingDeviceOrientationNotifications] 進行 hook 處理,通過 Crash必現的路徑,看到這個方法會發生在子線程中:

@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

也就是說在子線程中,觸發了該方法,然後系統監聽該通知做了UI操作,然後導致的Crash

問題解決

hook [UIDevice endGeneratingDeviceOrientationNotifications] 判斷執行該方法是否在主線程中執行,如果不是,則同步到主線程中轉發: (最終代碼)

@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

問題結束了?

上面的問題解決,其實的確是能解決問題,但是並沒有從根源上發現到底是什麼地方導致,通過查看代碼發現,我們的項目中對 [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications][[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications] 沒有成對實現,測試發現,如果 多調用了兩次 [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications] , 然後再喚起H5的拍照/錄視頻,在iOS13系統上必然Crash,可以在下載 HDCameraCrashDemo 進行驗證

可以在 [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications][[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications] 添加一個 BOOL 類型的變量來控制他們的成對出現,從根本上解決這類問題。

所以這個並不一定是iOS13系統的問題,只要在調用系統方法合理,並不會有該類型的Crash發生。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章