macOS 输入法的公用 API 参数可能会被系统喂 nil。

314 阅读3分钟

macOS 输入法的公用 API 参数可能会被系统喂 nil。一个最常见的情形:

  1. 点输入法选单「显示表情符号和其他符号」: 圖片.png
  2. 确保已经切换到要测试的输入法,然后在这个符号输入窗口当中使用鼠标点击输入任意符号。
  3. 你会亲眼看到输入法崩溃重启了,且生成了 console 错误报告:
    -------------------------------------
    Translated Report (Full Report Below)
    -------------------------------------

    Process:               输入法名称 [34680]
    Path:                  /Library/Input Methods/输入法名称.app/Contents/MacOS/输入法名称
    Identifier:            com.作者名称.inputmethod.输入法名称
    Version:               v114.514.1919810 (20230506052004)
    Code Type:             X86-64 (Native)
    Parent Process:        launchd [1]
    User ID:               502

    Date/Time:             2023-05-29 20:06:41.3789 +0800
    OS Version:            macOS 13.4 (22F66)
    Report Version:        12
    Bridge OS Version:     7.5 (20P5058)
    Anonymous UUID:        08378DA6-1F38-296F-EE86-338AA00FC385

    Sleep/Wake UUID:       1F6DE16D-2E2F-4637-8E75-E0F39DD20DE8

    Time Awake Since Boot: 85000 seconds
    Time Since Wake:       3258 seconds

    System Integrity Protection: enabled

    Crashed Thread:        0  Dispatch queue: com.apple.main-thread

    Exception Type:        EXC_BAD_INSTRUCTION (SIGILL)
    Exception Codes:       0x0000000000000001, 0x0000000000000000

    Termination Reason:    Namespace SIGNAL, Code 4 Illegal instruction: 4
    Terminating Process:   exc handler [34680]

    Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
    0   Fire                          	       0x10e504c02 0x10e4f8000 + 52226
    1   Fire                          	       0x10e505261 0x10e4f8000 + 53857
    2   InputMethodKit                	    0x7ff92b37170c -[IMKServer handleEvent_Common:characterIndex:edge:clientWrapper:controller:] + 697
    3   InputMethodKit                	    0x7ff92b36686e __63-[IMKServer handleEvent:characterIndex:edge:asyncClient:reply:]_block_invoke_2 + 676
    4   ViewBridge                    	    0x7ff822b83a4a +[NSServiceViewController withHostAppAuditToken:invoke:] + 92
    5   InputMethodKit                	    0x7ff92b3665be __63-[IMKServer handleEvent:characterIndex:edge:asyncClient:reply:]_block_invoke + 201
    6   InputMethodKit                	    0x7ff92b373146 __IMKXPCPerformBlockOnMainThread_block_invoke + 25
    7   CoreFoundation                	    0x7ff81adbdb71 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
    8   CoreFoundation                	    0x7ff81adbdaaa __CFRunLoopDoBlocks + 398
    9   CoreFoundation                	    0x7ff81adbcd7a __CFRunLoopRun + 2015
    10  CoreFoundation                	    0x7ff81adbbf31 CFRunLoopRunSpecific + 560
    11  HIToolbox                     	    0x7ff824837dad RunCurrentEventLoopInMode + 292
    12  HIToolbox                     	    0x7ff824837bbe ReceiveNextEventCommon + 657
    13  HIToolbox                     	    0x7ff824837918 _BlockUntilNextEventMatchingListInModeWithFilter + 64
    14  AppKit                        	    0x7ff81de505d0 _DPSNextEvent + 858
    15  AppKit                        	    0x7ff81de4f47a -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1214
    16  AppKit                        	    0x7ff81de41ae8 -[NSApplication run] + 586
    17  AppKit                        	    0x7ff81de15d02 NSApplicationMain + 817
    18  Fire                          	       0x10e4fb609 0x10e4f8000 + 13833
    19  dyld                          	    0x7ff81a98841f start + 1903

如果你胆子够肥,敢用 Xcode 给输入法玩实时侦错的话,你会发现 IMKInputController.handle() 当中首次读取 event 参数时的位置、就是输入法崩溃的那一刻。

对这种情形的解决方式就是在这个函式最开头插入这句即可:

guard let event = event else { return }

如果您的输入法是用 ObjC++ 写的,您得同时检测 nullPtr 与 NSNull:

- (BOOL)handleEvent:(NSEvent *)event client:(id)sender {
  if (event == nullptr || [event isEqual:[NSNull null]]) {
    return NO;
  }
...

如果您的输入法是用 ObjC 写的,您得同时检测 nil 与 NSNull:

- (BOOL)handleEvent:(NSEvent *)event client:(id)sender {
  if (event == nil || [event isEqual:[NSNull null]]) {
    return NO;
  }
...

类似情形可按照这个逻辑来处理。以上。

$ EOF.