iOS 11 bundleProxy 崩溃!= 使用 UNUserNotificationCenter 时出现 nil 错误
iOS 11 crashing with bundleProxy != nil error on using UNUserNotificationCenter
以下代码行是我们的应用程序在 iOS 11 / 11.0.1 / 11.0.2 / 11.1.1 / 11.2.2 对某些用户突然开始崩溃的地方:
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
我们在 didFinishLaunchingWithOptions
中找到了这个。崩溃报告说:
Fatal Exception: NSInternalInconsistencyException
Invalid parameter not satisfying: bundleProxy != nil
Fatal Exception: NSInternalInconsistencyException
0 CoreFoundation 0x1869b3d38 __exceptionPreprocess
1 libobjc.A.dylib 0x185ec8528 objc_exception_throw
2 CoreFoundation 0x1869b3c0c +[NSException raise:format:]
3 Foundation 0x187342c24 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:]
4 UserNotifications 0x18fcc973c -[UNUserNotificationCenter initWithBundleProxy:]
5 UserNotifications 0x18fcc950c __53+[UNUserNotificationCenter currentNotificationCenter]_block_invoke
6 libdispatch.dylib 0x186339048 _dispatch_client_callout
7 libdispatch.dylib 0x18633c710 dispatch_once_f$VARIANT$mp
8 UserNotifications 0x18fcc94ac +[UNUserNotificationCenter currentNotificationCenter]
它显然来自 iOS。还有其他人遇到同样的错误吗?知道发生了什么事吗?
我不确定这是否适用于所有人,但我已经为我的用例找到了它。我创建了 iOS 应用程序使用的框架。该框架使用 UNUserNotificationCenter
设置警报。似乎出于某种原因,当从框架内部使用此代码时, 'bundle' 未正确初始化。有时有效,有时无效。这个 bundleProxy
,听起来像是通知框架所依赖的某种代理。由于代码是从框架内部执行的,所以可能在运行时找不到这个包,并且系统 returns nil。我在尝试从包位置不正确的框架加载资源时遇到过这个问题。
无论如何,我的解决方案是在启动时在应用程序的委托中存储对 [UNUserNotificationCenter currentNotificationCenter]
的引用,然后将其传递给任何想要使用它的方法。当应用程序完成启动时,如果调用代码是应用程序的二进制文件本身,则此 proxy
似乎可以正确加载。这似乎已经为我解决了。
为什么
[UNUserNotificationCenter currentNotificationCenter]
有时会崩溃?
根据崩溃堆栈跟踪,UNUserNotificationCenter 的私有 init 方法中的 bundleIdentifier 为 nil 并引发异常。不知道为什么。
不幸的是,该方法是在 dispatch_once
上下文中调用的,因此我们无法轻易重现此崩溃。首先,我尝试使用 NSBundle 的方法:NSBundle.mainBundle.bundleIdentifier
,但失败了。我猜系统没有使用这个方法来获取bundleIdentifier,所以我尝试使用UNUserNotificationCenter的私有初始化方法initWithBundleIdentifier:(String)
,它起作用并试图将nil传递给这个方法,导致100%的时间崩溃!! !!所以我们可以在文件加载时使用这个方法,如果bundleIdentifier==nil
return nil,希望对你有帮助。
----------------代码----------------
/* UNUserNotificationCenter + Hack */
@implementation UNUserNotificationCenter (Hack)
+ (void)load {
static dispatch_once_t _onceToken;
dispatch_once(&_onceToken, ^{
[self safeHook];
});
}
+ (void)safeHook {
/*hook UNUserNotificationCenter's systemMethod initWithBundleIdentifier:*/
/* private method mix,hope no runtime check */
NSString * orig_initWithBundleSelectorName = [NSString stringWithFormat:@"%@%@%@",@"initWi",@"thBundleId",@"entifier:"];
SEL orig_initWithBundleSelector = NSSelectorFromString(orig_initWithBundleSelectorName);
if (![self instancesRespondToSelector:orig_initWithBundleSelector]) {
return;
}
SEL alt_initWithBundleSelector = @selector(ht_initWithBundleIdentifier:);
Method origMethod = class_getInstanceMethod(self, orig_initWithBundleSelector);
Method altMethod = class_getInstanceMethod(self, @selector(ht_initWithBundleIdentifier:));
class_addMethod(self,
orig_initWithBundleSelector,
class_getMethodImplementation(self, orig_initWithBundleSelector),
method_getTypeEncoding(origMethod));
class_addMethod(self,
alt_initWithBundleSelector,
class_getMethodImplementation(self, alt_initWithBundleSelector),
method_getTypeEncoding(altMethod));
method_exchangeImplementations(origMethod, altMethod);
}
- (instancetype)ht_initWithBundleIdentifier:(id)identifier {
if (nil==identifier||NSNull.null==identifier) {
return nil;
}
/* you can test, if give nil to this method ,100% crash!!!!*/
/* [self ht_initWithBundleIdentifier:nil] 100% crash!!!!*/
return [self ht_initWithBundleIdentifier:identifier];
}
@end
OneSignal 也遇到过这种情况。他们的解决方法是检查当前进程名称,如果其中包含 IBDesignable
,则尽早 return。它对我也有用。
对于那些在这里使用 PacketTunnelProvider NetworkExtension 的人来说,如果您从 stopTunnelWithReason
调用它,可能会发生这种情况。取而代之的是之前获取 currentNotificationCenter
并保存它。
以下代码行是我们的应用程序在 iOS 11 / 11.0.1 / 11.0.2 / 11.1.1 / 11.2.2 对某些用户突然开始崩溃的地方:
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
我们在 didFinishLaunchingWithOptions
中找到了这个。崩溃报告说:
Fatal Exception: NSInternalInconsistencyException
Invalid parameter not satisfying: bundleProxy != nil
Fatal Exception: NSInternalInconsistencyException
0 CoreFoundation 0x1869b3d38 __exceptionPreprocess
1 libobjc.A.dylib 0x185ec8528 objc_exception_throw
2 CoreFoundation 0x1869b3c0c +[NSException raise:format:]
3 Foundation 0x187342c24 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:]
4 UserNotifications 0x18fcc973c -[UNUserNotificationCenter initWithBundleProxy:]
5 UserNotifications 0x18fcc950c __53+[UNUserNotificationCenter currentNotificationCenter]_block_invoke
6 libdispatch.dylib 0x186339048 _dispatch_client_callout
7 libdispatch.dylib 0x18633c710 dispatch_once_f$VARIANT$mp
8 UserNotifications 0x18fcc94ac +[UNUserNotificationCenter currentNotificationCenter]
它显然来自 iOS。还有其他人遇到同样的错误吗?知道发生了什么事吗?
我不确定这是否适用于所有人,但我已经为我的用例找到了它。我创建了 iOS 应用程序使用的框架。该框架使用 UNUserNotificationCenter
设置警报。似乎出于某种原因,当从框架内部使用此代码时, 'bundle' 未正确初始化。有时有效,有时无效。这个 bundleProxy
,听起来像是通知框架所依赖的某种代理。由于代码是从框架内部执行的,所以可能在运行时找不到这个包,并且系统 returns nil。我在尝试从包位置不正确的框架加载资源时遇到过这个问题。
无论如何,我的解决方案是在启动时在应用程序的委托中存储对 [UNUserNotificationCenter currentNotificationCenter]
的引用,然后将其传递给任何想要使用它的方法。当应用程序完成启动时,如果调用代码是应用程序的二进制文件本身,则此 proxy
似乎可以正确加载。这似乎已经为我解决了。
为什么
[UNUserNotificationCenter currentNotificationCenter]
有时会崩溃?
根据崩溃堆栈跟踪,UNUserNotificationCenter 的私有 init 方法中的 bundleIdentifier 为 nil 并引发异常。不知道为什么。
不幸的是,该方法是在 dispatch_once
上下文中调用的,因此我们无法轻易重现此崩溃。首先,我尝试使用 NSBundle 的方法:NSBundle.mainBundle.bundleIdentifier
,但失败了。我猜系统没有使用这个方法来获取bundleIdentifier,所以我尝试使用UNUserNotificationCenter的私有初始化方法initWithBundleIdentifier:(String)
,它起作用并试图将nil传递给这个方法,导致100%的时间崩溃!! !!所以我们可以在文件加载时使用这个方法,如果bundleIdentifier==nil
return nil,希望对你有帮助。
----------------代码----------------
/* UNUserNotificationCenter + Hack */
@implementation UNUserNotificationCenter (Hack)
+ (void)load {
static dispatch_once_t _onceToken;
dispatch_once(&_onceToken, ^{
[self safeHook];
});
}
+ (void)safeHook {
/*hook UNUserNotificationCenter's systemMethod initWithBundleIdentifier:*/
/* private method mix,hope no runtime check */
NSString * orig_initWithBundleSelectorName = [NSString stringWithFormat:@"%@%@%@",@"initWi",@"thBundleId",@"entifier:"];
SEL orig_initWithBundleSelector = NSSelectorFromString(orig_initWithBundleSelectorName);
if (![self instancesRespondToSelector:orig_initWithBundleSelector]) {
return;
}
SEL alt_initWithBundleSelector = @selector(ht_initWithBundleIdentifier:);
Method origMethod = class_getInstanceMethod(self, orig_initWithBundleSelector);
Method altMethod = class_getInstanceMethod(self, @selector(ht_initWithBundleIdentifier:));
class_addMethod(self,
orig_initWithBundleSelector,
class_getMethodImplementation(self, orig_initWithBundleSelector),
method_getTypeEncoding(origMethod));
class_addMethod(self,
alt_initWithBundleSelector,
class_getMethodImplementation(self, alt_initWithBundleSelector),
method_getTypeEncoding(altMethod));
method_exchangeImplementations(origMethod, altMethod);
}
- (instancetype)ht_initWithBundleIdentifier:(id)identifier {
if (nil==identifier||NSNull.null==identifier) {
return nil;
}
/* you can test, if give nil to this method ,100% crash!!!!*/
/* [self ht_initWithBundleIdentifier:nil] 100% crash!!!!*/
return [self ht_initWithBundleIdentifier:identifier];
}
@end
OneSignal 也遇到过这种情况。他们的解决方法是检查当前进程名称,如果其中包含 IBDesignable
,则尽早 return。它对我也有用。
对于那些在这里使用 PacketTunnelProvider NetworkExtension 的人来说,如果您从 stopTunnelWithReason
调用它,可能会发生这种情况。取而代之的是之前获取 currentNotificationCenter
并保存它。