公司项目在升级到iOS 13后频繁出现NSInternalInconsistencyException:
threading violation: expected the main thread的崩溃现象,在bugly上能看到不断上报的崩溃记录,但自己本地怎么都复现不了,看着很揪心。

问题分析

bugly上的错误堆栈信息如下,调用栈都是系统的API,只能分析分析看能不能找到一些蛛丝马迹了。

其中的[UIDevice endGeneratingDeviceOrientationNotifications]引起了我的注意,应该是因为某种原因在非主线程调用了这个方法,造成了threading violation。为了验证我的想法,我用hook的方式在调用[UIDevice endGeneratingDeviceOrientationNotifications]时检查当前是否为主线程,结果在某些情况下真的会出现。验证代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@implementation UIDevice (VK)

static inline void vk_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 {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
vk_swizzleSelector(UIDevice.class, @selector(endGeneratingDeviceOrientationNotifications), @selector(vk_EndGeneratingDeviceOrientationNotifications));
}
});
}

- (void)vk_EndGeneratingDeviceOrientationNotifications {
NSLog(@"endGeneratingDeviceOrientationNotifications isMainThread:%d", [NSThread isMainThread]);
[self endGeneratingDeviceOrientationNotifications];
}

@end

解决方法

既然知道了crash的原因,解决方法也就有了,在上述代码的基础上,把[self endGeneratingDeviceOrientationNotifications]放到主线程执行就好了,最终实现代码如下:

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
@implementation UIDevice (VK)

static inline void vk_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 {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
vk_swizzleSelector(UIDevice.class, @selector(endGeneratingDeviceOrientationNotifications), @selector(vk_EndGeneratingDeviceOrientationNotifications));
}
});
}

- (void)vk_EndGeneratingDeviceOrientationNotifications {
if ([NSThread isMainThread]) {
[self endGeneratingDeviceOrientationNotifications];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self endGeneratingDeviceOrientationNotifications];
});
}
}
@end

真的只是iOS的锅?

Google一下,网上碰到类似问题的人很多,基本都是甩锅给iOS 13。可偏偏为什么是[UIDevice endGeneratingDeviceOrientationNotifications]会出现这个问题呢?我们知道,与之对应的方法是[UIDevice beginGeneratingDeviceOrientationNotifications],需要成对使用。检查了一下代码,发现真的有一个地方多调用了一次endGeneratingDeviceOrientationNotifications,可能这才是问题的根本原因。