1

我的 iOS 应用程序使用 UIMenuController 来显示复制/粘贴上下文菜单。当我在 macOS 12.0 上启动应用程序并使用鼠标或触控板控制单击(右键单击)时,应用程序在显示带有以下崩溃日志的菜单时崩溃:

Application Specific Information:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 
'*** -[__NSDictionaryM setObject:forKey:]: object cannot be nil (key: title)'

Last Exception Backtrace:
0   CoreFoundation                         0x1be758118 __exceptionPreprocess + 220
1   libobjc.A.dylib                        0x1be4a9808 objc_exception_throw + 60
2   CoreFoundation                         0x1be828464 -[__NSCFString characterAtIndex:].cold.1 + 0
3   CoreFoundation                         0x1be835270 -[__NSDictionaryM setObject:forKey:].cold.3 + 0
4   CoreFoundation                         0x1be691590 -[__NSDictionaryM setObject:forKey:] + 904
5   UIKitCore                              0x1e5b85998 -[_UIMenuBarItem properties] + 124
6   UIKitMacHelper                         0x1d3bc7058 UINSNSMenuItemFromUINSMenuItem + 96
7   UIKitMacHelper                         0x1d3bc6d60 _insertUINSMenuItemsIntoNSMenu + 844
8   UIKitMacHelper                         0x1d3bc67c0 UINSNSMenuFromUINSMenu + 152
9   UIKitMacHelper                         0x1d3bc6690 -[UINSMenuController _createNSMenu:forContextMenu:] + 92
10  UIKitMacHelper                         0x1d3c3505c -[UINSMenuController _prepareToShowContextMenu:activityItemsConfiguration:] + 144
11  UIKitMacHelper                         0x1d3c349c0 -[UINSMenuController showContextMenu:inWindow:atLocationInWindow:activityItemsConfiguration:] + 312
12  libdispatch.dylib                      0x1be44ce60 _dispatch_call_block_and_release + 32
13  libdispatch.dylib                      0x1be44ebac _dispatch_client_callout + 20
14  libdispatch.dylib                      0x1be45d0ac _dispatch_main_queue_callback_4CF + 944
15  CoreFoundation                         0x1be719e60 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16

我对其他开发人员的几个 iOS 应用程序进行了同样的尝试,当我用鼠标右键单击时,它们都在 macOS 上崩溃。

有没有人找到解决方法?

4

1 回答 1

2

properties可以通过调整私有_UIMenuBarItem类的方法来解决这个问题。显然,这带有通常的免责声明,这可能会让你被 Apple 拒绝(但实际上这似乎不会经常导致拒绝)。

这是修复:基本思想是将原始方法调用包装在一个@try/@catch块中。发生崩溃是因为原始实现有时会尝试将键的nil值插入title到 NSDictionary 中。此解决方法会捕获该异常,然后返回一个虚拟字典以满足调用者的要求。

UIMenuBarItemMontereyCrashFix.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

/// Helper class to apply a fix to prevent a crash on macOS Monterey when a user right-clicks in a text field
@interface UIMenuBarItemMontereyCrashFix : NSObject

/// Apply the crash fix. It will only be applied the first time it's called, subsequent calls are no-ops.
/// It will only have an effect when called on macOS Monterey or later.
+ (void)applyCrashFixIfNeeded;

@end

NS_ASSUME_NONNULL_END

UIMenuBarItemMontereyCrashFix.m

#import "UIMenuBarItemMontereyCrashFix.h"
#import <objc/runtime.h>

static BOOL hasCrashFixBeenApplied = NO;

@implementation UIMenuBarItemMontereyCrashFix

/// Apply the crash fix. It will only be applied the first time it's called, subsequent calls are no-ops.
+ (void)applyCrashFixIfNeeded
{
    if (@available(macOS 12.0, *)) {} else {
        // Bail if we are not running on Monterey
        return;
    }
    
    if (!hasCrashFixBeenApplied) {
        Class UnderscoreUIMenuBarItem = NSClassFromString(@"_UIMenuBarItem");
        SEL selector = sel_getUid("properties");
        Method method = class_getInstanceMethod(UnderscoreUIMenuBarItem, selector);
        IMP original = method_getImplementation(method);
        
        // The crash happens because in some instances the original implementation
        // tries to insert `nil` as a value for the key `title` into a dictionary.
        // This is how the fix works:
        // We wrap the original implementation call in a @try/@catch block. When the
        // exception happens, we catch it, and then return a dummy dictionary to
        // satisfy the caller. The dummy has `isEnabled` set to NO, and `isHidden` set
        // to YES.
        IMP override = imp_implementationWithBlock(^id(id me) {
            @try {
                id res = ((id (*)(id))original)(me);
                return res;
            }
            @catch(NSException *exception) {
                return @{
                    @"allowsAutomaticKeyEquivalentLocalization" : @0,
                    @"allowsAutomaticKeyEquivalentMirroring" : @0,
                    @"defaultCommand" : @0,
                    @"identifier":@"com.apple.menu.application",
                    @"imageAlwaysVisible" : @0,
                    @"isAlternate" : @0,
                    @"isEnabled" : @0,
                    @"isHidden" : @1,
                    @"isSeparatorItem" : @0,
                    @"keyEquivalent" : @"",
                    @"keyEquivalentModifiers" : @0,
                    @"remainsVisibleWhenDisabled" : @0,
                    @"state" : @0,
                    @"title" : @""
                };
            }
        });
        method_setImplementation(method, override);
        
        hasCrashFixBeenApplied = YES;
    }
}

@end

请记住添加UIMenuBarItemMontereyCrashFix.h到您的桥接头中,以便您可以从 Swift 中调用它。然后只需UIMenuBarItemMontereyCrashFix.applyIfNeeded()在您的应用程序启动序列中的某个地方调用(例如在您的 AppDelegate 中)。

于 2021-11-30T16:03:43.447 回答