macOS WebView JSToNative
项目里通过使用的是WebView 并且通过 - (void)webView:(WebView *)webView windowScriptObjectAvailable:(WebScriptObject *)windowScriptObject
这个方法实现JS到Native的通信。
项目中代码如下:
1 2 3 4 5 6
| self.mainWebView.frameLoadDelegate = self;
# pragma mark - WebFrameLoadDelegate - (void)webView:(WebView *)webView windowScriptObjectAvailable:(WebScriptObject *)windowScriptObject { [windowScriptObject setValue:self forKey:@"jsToNative"]; }
|
这样会形成循环引用,self => self.mainWebView => self.mainWebView. windowScriptObject => self
可以如下修改解决问题:
1 2 3 4 5 6
| self.jsToNativeObject = [[JSToNativeObject alloc] initWithDelegate:self];
# pragma mark - WebFrameLoadDelegate - (void)webView:(WebView *)webView windowScriptObjectAvailable:(WebScriptObject *)windowScriptObject { [windowScriptObject setValue:self.jsToNativeObject forKey:@"jsToNative"]; }
|
JSToNativeObject.h
1 2 3 4 5 6 7 8
| @interface JSToNativeObject : NSObject
- (instancetype)initWithDelegate:(id<JSToNativeWebViewProtocol>)delegate;
@property (nonatomic,weak,readonly) id<JSToNativeWebViewProtocol>delegate;
@end
|
JSToNativeObject.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| - (instancetype)initWithDelegate:(id<JSToNativeWebViewProtocol>)delegate { self = [super init]; if (self) { self.delegate = delegate; } return self; }
- (id)invokeDefaultMethodWithArguments:(NSArray *)arguments { if (arguments.count < 2) { return nil; } NSString *methodStr = arguments[0];
NSString *paramsStr = arguments[1]; SEL sel; if ([methodStr isEqualToString:@"xxxxxx"]) { sel = @selector(xxxxxx:); }else { sel = nil; } return [self handleSelector:sel withObject: paramsStr]; }
- (id)handleSelector:(SEL)aSelector withObject:(NSDictionary *)params { if (aSelector != nil) { if ([self.delegate respondsToSelector:aSelector]) { NSMethodSignature *signature = [[self.delegate class] instanceMethodSignatureForSelector:aSelector]; if (signature == nil) { NSLog(@"方法签名不存在"); } # pragma clang diagnostic push # pragma clang diagnostic ignored "-Warc-performSelector-leaks" id obj = [self.delegate performSelector:aSelector withObject:params]; # pragma clang diagnostic pop if (signature.methodReturnLength) { return obj; } } }
return nil; }
|
改进版
上面的代码有崩溃问题,原因见 performSelector 那篇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| - (id)handleSelector:(SEL)aSelector withObject:(NSDictionary *)params { if (aSelector != nil) { if ([self.delegate respondsToSelector:aSelector]) { NSMethodSignature *signature = [[self.delegate class] instanceMethodSignatureForSelector:aSelector]; if (signature == nil) { NSLog(@"方法签名不存在"); return nil; } if (params == nil) { params = @{}; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if (signature.methodReturnLength > 0) { return [self.delegate performSelector:aSelector withObject:params]; }else { [self.delegate performSelector:aSelector withObject:params]; } #pragma clang diagnostic pop
} } return nil; }
|
升级版
可以接受并处理多个参数,区分有无返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| - (id)handleSelector:(SEL)aSelector withObject:(NSDictionary *)params { if (aSelector != nil) { if ([self.delegate respondsToSelector:aSelector]) { NSMethodSignature *signature = [[self.delegate class] instanceMethodSignatureForSelector:aSelector]; if (signature == nil) { NSLog(@"方法签名不存在"); return nil; } if (params == nil) { params = @{}; } NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; invocation.target = self.delegate; invocation.selector = aSelector; self.objects = @[params]; NSInteger paramsCount = signature.numberOfArguments - 2; paramsCount = MIN(paramsCount, self.objects.count); for (NSInteger i = 0; i < paramsCount; i++) { id object = self.objects[i]; if ([object isKindOfClass:[NSNull class]]) continue; [invocation setArgument:&object atIndex:i + 2]; } [invocation invoke]; void *result = NULL;
if (signature.methodReturnLength) { [invocation getReturnValue:&result]; } if (result == NULL) { return nil; } id returnValue; returnValue = (__bridge id)result; return returnValue; } } return nil; }
|
方法废弃
问题是解决了,但是上面的web view 的- (void)webView:(WebView *)webView windowScriptObjectAvailable:(WebScriptObject *)windowScriptObject
已经废弃了 官方推荐使用 - (void)webView:(WebView *)webView didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame
;
又但是 WebView 官方也不推荐使用了支持到10.14了,让大家使用 WKWebView (WK_EXTERN API_AVAILABLE(macosx(10.10), ios(8.0)))
。另推荐使用 JavaScriptCore