美文网首页程序员iOS Developer编程知识点
iOS-JSBrigde:JS和Native通信实现的两种方式

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

作者: 费宇超 | 来源:发表于2017-03-27 11:49 被阅读695次

    现在纯原生的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通信实现的两种方式

        本文链接:https://www.haomeiwen.com/subject/tssilttx.html