WKWebView子类按ESC键触发_cancelOperation:未识别选择器异常
_cancelOperation:未识别选择器异常的问题与解决 问题描述
在iOS混合WebView应用中,使用自定义WKWebView子类MyBrandWebView时出现异常:当WebView内输入框聚焦时按下ESC键,会抛出以下NSException:
Exception NSException * "-[App.MyBrandWebView _cancelOperation:]: unrecognized selector sent to instance 0x119027400" 0x0000600000cfb9c0
尝试过的无效方案
最初尝试在子类中实现_cancelOperation:和cancelOperation:方法,但无法解决问题,代码如下:
class MyBrandWebView: WKWebView { @objc func _cancelOperation(sender: Any?) { } @objc func cancelOperation(sender: Any?) { } }
堆栈跟踪
*** First throw call stack: ( 0 CoreFoundation 0x00000001804658a8 __exceptionPreprocess + 172 1 libobjc.A.dylib 0x000000018005c09c objc_exception_throw + 56 2 CoreFoundation 0x000000018047a6f8 +[NSObject(NSObject) instanceMethodSignatureForSelector:] + 0 3 UIKitCore 0x0000000110a7b3d4 -[UIResponder doesNotRecognizeSelector:] + 232 4 CoreFoundation 0x00000001804699f8 ___forwarding___ + 1280 5 CoreFoundation 0x000000018046bd1c _CF_forwarding_prep_0 + 92 6 UIKitCore 0x000000011084fe28 -[UIKeyboardImpl _cancelOperation:testOnly:] + 636 7 UIKitCore 0x00000001108512a8 -[UIKeyboardImpl handleKeyCommand:repeatOkay:options:] + 3760 8 UIKitCore 0x0000000110880e80 -[UIKeyboardImpl _handleKeyCommandCommon:options:] + 72 9 UIKitCore 0x0000000110880e28 -[UIKeyboardImpl _handleKeyCommand:] + 16 10 UIKitCore 0x00000001108665b4 -[UIKeyboardImpl handleKeyTextCommandForCurrentEvent] + 236 11 WebKit 0x0000000107d8b3bc -[WKContentView(WKInteraction) _interpretKeyEvent:isCharEvent:] + 160 12 WebKit 0x000000010789f8c4 _ZN6WebKit14PageClientImpl17interpretKeyEventERKNS_22NativeWebKeyboardEventEb + 48 13 WebKit 0x00000001078c4cb0 _ZN6WebKit12WebPageProxy17interpretKeyEventERKNS_11EditorStateEbON3WTF17CompletionHandlerIFvbEEE + 140 14 WebKit 0x0000000107cda790 _ZN6WebKit12WebPageProxy21didReceiveSyncMessageERN3IPC10ConnectionERNS1_7DecoderERN3WTF9UniqueRefINS1_7EncoderEEE + 3268 15 WebKit 0x0000000107dc260c _ZN3IPC18MessageReceiverMap19dispatchSyncMessageERNS_10ConnectionERNS_7DecoderERN3WTF9UniqueRefINS1_7EncoderEEE + 276 16 WebKit 0x00000001079eb928 _ZN6WebKit15WebProcessProxy21didReceiveSyncMessageERN3IPC10ConnectionERNS1_7DecoderERN3WTF9UniqueRefINS1_7EncoderEEE + 40 17 WebKit 0x0000000107dbe6f0 _ZN3IPC10Connection15dispatchMessageENSt3__110unique_ptrINS_7DecoderENS1_14default_deleteIS3_EEEE + 728 18 WebKit 0x0000000107dbff58 _ZN3WTF6Detail15CallableWrapperIZN3IPC10Connection16SyncMessageState22processIncomingMessageERS3_RNSt3__110unique_ptrINS2_7DecoderENS6_14default_deleteIS8_EEEEE3$_5vJEE4callEv + 636 19 JavaScriptCore 0x000000010d2dc3d4 _ZN3WTF7RunLoop11performWorkEv + 176 20 JavaScriptCore 0x000000010d2dd0e4 _ZN3WTF7RunLoop11performWorkEPv + 32 21 CoreFoundation 0x00000001803c669c __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24 22 CoreFoundation 0x00000001803c65e4 __CFRunLoopDoSource0 + 172 23 CoreFoundation 0x00000001803c5d54 __CFRunLoopDoSources0 + 232 24 CoreFoundation 0x00000001803c043c __CFRunLoopRun + 768 25 CoreFoundation 0x00000001803bfd28 CFRunLoopRunSpecific + 572 26 GraphicsServices 0x000000018986ebc0 GSEventRunModal + 160 27 UIKitCore 0x0000000110a4ffdc -[UIApplication _run] + 868 28 UIKitCore 0x0000000110a53c54 UIApplicationMain + 124 29 App 0x00000001001fc684 main + 64 30 dyld 0x00000001003d1558 start_sim + 20 31 ??? 0x00000001004b6058 0x0 + 4299907160 32 ??? 0x8343000000000000 0x0 + 9458403642408173568 ) libc++abi: terminating due to uncaught exception of type NSException
原因分析
从堆栈跟踪可见,ESC键触发的事件经UIKit、WebKit处理后,通过响应链传递到MyBrandWebView,系统尝试调用私有方法_cancelOperation:时,子类的Swift实现无法匹配Objective-C的方法签名——Swift中声明的sender: Any?会自动添加参数标签,生成的选择器是_cancelOperationWithSender:,而非系统期望的_cancelOperation:。此外,WKWebView的私有实现依赖响应链传递该消息,直接在子类添加方法无法覆盖系统的调用逻辑。
解决方案
方案1:Objective-C分类添加方法
通过Objective-C分类为WKWebView添加匹配签名的_cancelOperation:方法,该分类会作用于所有WKWebView及其子类:
// WKWebView+CancelOperation.h #import <WebKit/WebKit.h> @interface WKWebView (CancelOperation) @end // WKWebView+CancelOperation.m #import "WKWebView+CancelOperation.h" @implementation WKWebView (CancelOperation) - (void)_cancelOperation:(id)sender { // 空实现即可避免异常 } @end
方案2:Swift子类重写消息转发方法
在MyBrandWebView中重写消息转发相关方法,拦截未识别的_cancelOperation:选择器:
class MyBrandWebView: WKWebView { override func forwardInvocation(_ invocation: NSInvocation) { let selector = invocation.selector if selector == #selector(_cancelOperation(_:)) { // 忽略该调用 return } super.forwardInvocation(invocation) } override func methodSignature(for selector: Selector!) -> NSMethodSignature! { if selector == #selector(_cancelOperation(_:)) { // 匹配系统期望的方法签名:返回值void,参数为id return NSMethodSignature.signature(withObjCTypes: "v@:@") } return super.methodSignature(for: selector) } @objc private func _cancelOperation(_ sender: Any?) { // 空实现 } }
内容的提问来源于stack exchange,提问作者Jesse Onolemen

