iOS-JSBrigde:JS和Native通信实现的两种方式

现在纯原生的APP是越来越少了,更多的都加入了Web混合开发(有UiWebView和iOS 8之后的WKWebView,后者在性能上优化了许多),甚至有些直接是WebAPP。JSPatch、weex以及ReactNative等热更新技术也相当流行,虽然2017年3月份苹果的一封邮件警告,让许多使用者对这些热更新技术有点害怕。
既然用到了web,就避免不了JS和OC语言的互相调用。
本文介绍自己项目中的js-oc互相调用实现方法。从一开始简单的url拦截到最近的基于JavaScriptcore建立的JSbrige两套方案。

URL拦截方式

JS调用OC:


- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    
    NSString * currentUrl = webView.request.URL.absoluteString;
    //判断是否是单击
    if (navigationType == UIWebViewNavigationTypeLinkClicked)
    {
        NSURL *url = [request URL];
        NSLog(@"当前url%@",url);
        NSString *urlStr = url.absoluteString;
        if ([[url scheme] isEqualToString:@"cloudsc6"]){
            if ([[url host] isEqualToString:@"PLAYVOICE"]) {
                NSLog(@"准备播放语音消息");
            }
        }
            else {
                return YES;
            }
    }
    return YES;
}

这个实现方式事实上有很多局限,首先参数的传递就不太友好,参数放在url中实现实在不是很优雅的实现方式。
而且,工作中实测,当JS实现事件的方式是Ajax异步的时候,有些事件这个代理是拦截不到的。
当时解决的方案是使用了NSURLProtocol全局去拦截url请求。如下

@implementation InnerResourcesURLProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    BOOL can = NO;
    //NSLog(@"--->can handled Request? #%lu: URL = %@", (unsigned long)requestCount++, request);
  //  NSLog(@"%@",request);
    if([NSURLProtocol propertyForKey:CLOUDSEE_INNER_PROTOCOL_REQ_HANDLED_KEY inRequest:request]){
        can =  NO;
    }else {
        NSURL *url = [request URL];
        if(([[url scheme] isEqualToString:DEFAULT_SCHEMA])||([[url scheme]isEqualToString:@"th"])){
            can = YES;
        }
    }
    return can;
}
- (void) startLoading
{
    AppDelegate *dd = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    NSURL *url = [self.request URL];
    if ([[url scheme] isEqualToString:@"PLAYVOICE"]) {
    }
}

同样地也有其局限性。

OC调用JS

-(void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSString *scriptResult = [self.webView stringByEvaluatingJavaScriptFromString:@"didWebViewScroll()"];
    if([scriptResult isEqualToString:@"NO"]) {
        self.webView.scrollView.bounces=NO;
    }
}

这个实现方式简单,但是同样也相当不友好。首先传参数在didWebViewScroll()中的()实现,不仅对长度有限,而且如果参数有含有''会导致截取错误代码。

而且这个方案下,有些事件安卓能截取,iOS 不能截取。有些事件iOS 能截取,安卓不能截取。跨平台性也不好。

WebViewJavascriptBridge

网上有一个著名的库,星星数已达8823。但是其底层实现原理还是基于URL拦截方式去实现。
再次证明星星数不能代表什么,只能说其比较出名。

JavaScriptcore桥接实现

如果你的APP不需要支持iOS 7之前的版本,可以使用以下方案,我们公司现在用的就是这个方案。
苹果在iOS 7中加入了JavaScriptCore框架。该框架让Objective-C和JavaScript代码直接的交互变得更加的简单方便。

桥接关键 和JS调用OC

-(void)webViewDidFinishLoad:(UIWebView *)webView{
    self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    //打印JS异常
    [self.jsContext setExceptionHandler:^(JSContext *context, JSValue *value) {
        NSLog(@"oc catches the exception: %@", value);
    }];
    //以下是桥接JS与OC的关键
    JSModel * model = [[JSModel alloc]init];
    model.controller = self;
    model.JSContext = self.jsContext;
    //以下是H5中调用的方法名称
    NSString * key = @"DDJSBridge";
    [self.jsContext setObject:model forKeyedSubscript:key];
    JSValue * loadData = self.jsContext[@"loadData"];
    //以下是回调JS方法,可以传入封装好的参数
    [loadData callWithArguments:@[self.jsonStr]];
    if (isLoadData) {
        [self firstIn];
    }
    isLoadData = NO;
    // 以下是执行JS方法
    // [self.jsContext evaluateScript:self.htmlCont];
}

OC调用JS

只要将JSModel继承JSExport,再本地实现以下方法

-(void)call:(NSString *)methodName :(NSDictionary *)params :(JSValue *)callBack{
    NSDictionary * data = [params objectForKey:@"data"];
    if ([methodName isEqualToString:@"showShareButton"]) {
        NSString * iconsStrUrl = [data objectForKey:@"icon"];
        if (iconsStrUrl) {
            NSURL * iconUrl = [NSURL URLWithString:iconsStrUrl];
            NSData * imageData = [NSData dataWithContentsOfURL:iconUrl];
            UIImage *image = [UIImage imageWithData:imageData scale:3];
            UIBarButtonItem *rightBarButton = [[UIBarButtonItem alloc]
                                               initWithImage:image
                                               style:UIBarButtonItemStylePlain
                                               target:self
                                               action:@selector(rightClick)];
            self.controller.navigationItem.rightBarButtonItem = rightBarButton;
        }
}

至于跨平台性,也比第一个方案要友好,安卓也有类似的实现方案。

第二个方案的DEMO已经上传,可以下载
JSBrigeOC版本
JSBrigeSwift版本

JavascriptBridge实现内部原理

有一个百度团队的博客写的很好,可以参考。
深入浅出 JavaScriptCore
浅谈JavaScriptCore

阿里无限团队接口实现规范

我们也是基本按照它的接口规范的
阿里无限团队接口实现规范

Weex 是如何在 iOS 客户端上跑起来的

Weex 是如何在 iOS 客户端上跑起来的

你可能感兴趣的:(iOS-JSBrigde:JS和Native通信实现的两种方式)