iOS 11 在使用 UNUserNotificationCenter 时因 bundleProxy 崩溃!= nil 错误

Posted

技术标签:

【中文标题】iOS 11 在使用 UNUserNotificationCenter 时因 bundleProxy 崩溃!= nil 错误【英文标题】:iOS 11 crashing with bundleProxy != nil error on using UNUserNotificationCenter 【发布时间】:2018-03-17 15:38:41 【问题描述】:

以下代码行是我们的应用突然开始在 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。还有其他人遇到同样的错误吗?知道发生了什么吗?

【问题讨论】:

你弄明白了吗? 不,仍然看到神秘的崩溃 @SamJarman 我想是的 【参考方案1】:

我不确定这是否适用于所有人,但我已经为我的用例找到了答案。我创建了一个 iOS 应用程序使用的框架。这个框架使用UNUserNotificationCenter 来设置警报。似乎由于某种原因,当从框架内部使用此代码时,“捆绑包”没有正确初始化。有时有效,有时无效。从事物的声音来看,这个bundleProxy 是通知框架所依赖的某种代理。由于代码是从框架内部执行的,因此可能在运行时找不到此捆绑包并且系统返回 nil。在尝试从捆绑包位置不正确的框架中加载资源时,我通常会遇到此问题。

无论如何,我的解决方案是在启动时在应用程序的委托中存储对[UNUserNotificationCenter currentNotificationCenter] 的引用,然后将其传递给任何想要使用它的方法。当应用程序完成启动时,如果调用代码是应用程序的二进制文件本身,则此 proxy 似乎可以正确加载。这似乎已经为我解决了。

【讨论】:

我遇到了同样的问题 bundleProxy != nil 断言失败。导致它的调用是 AppDelegate.didFinishLaunching 中的 UNUserNotificationCenter.current(),这似乎是您的建议。你能发布你的代码吗? 否,但如前所述,请确保您的代码未在框架内运行。如果是,你需要从你的应用的委托中传递一个引用。 它在一个框架中,在应用程序的委托中是我试图获取 UNUserNotificationCenter.current() 的地方,但那是调用失败。 项目中没有AppDelegate。我在对框架/SDK 项目进行单元测试时遇到此错误。 @Anees 然后在单元测试设置方法中创建并初始化它,可能是类设置方法【参考方案2】:

为什么 [UNUserNotificationCenter currentNotificationCenter] 有时会崩溃?

根据崩溃堆栈跟踪,UNUserNotificationCenter 的私有 init 方法中的 bundleIdentifier 为 nil,并引发了异常。我不知道为什么。

不幸的是,该方法是在dispatch_once 上下文中调用的,因此我们无法轻松重现此崩溃。首先,我尝试使用NSBundle的方法:NSBundle.mainBundle.bundleIdentifier,但是失败了。估计系统没有使用这个方法获取bundleIdentifier,所以我尝试使用UNUserNotificationCenter的私有init方法initWithBundleIdentifier:(String),成功了,并尝试将nil传递给这个方法,导致100%的时候crash!!!所以我们可以在文件加载的时候使用这个方法,如果bundleIdentifier==nil返回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

【讨论】:

这个解决方案很棒,但并没有完全解决问题。使用此方法后,我的一些用户仍然会偶尔遇到崩溃。崩溃问题是Invalid parameter not satisfying: bundleProxy != nil【参考方案3】:

OneSignal 也经历过这种情况。他们的解决方法是检查当前进程名称,以及它是否包含IBDesignable 以便提前返回。它也对我有用。

GitHub issue

Fix commit

【讨论】:

【参考方案4】:

对于那些在这里使用 PacketTunnelProvider NetworkExtension 的人,如果您从 stopTunnelWithReason 调用它,可能会发生这种情况。而是先获取currentNotificationCenter 并保存。

【讨论】:

以上是关于iOS 11 在使用 UNUserNotificationCenter 时因 bundleProxy 崩溃!= nil 错误的主要内容,如果未能解决你的问题,请参考以下文章

iOS 11 在使用 UNUserNotificationCenter 时因 bundleProxy 崩溃!= nil 错误

在 iOS 11 中使用增加的导航栏标题

iOS 11 - 使用大标题模式时的 UINavigationItem titleView

iOS 上的 IDFA 和 IDFV 仍在使用 XCode 11 构建

如何使用 React Native 重现 iOS 11 地图底页

UITableViewCell 使用 separatorInset 隐藏分隔符在 iOS 11 中失败