iOS与JS交互总结

作者: 凌巅 | 来源:发表于2017-05-02 15:17 被阅读194次

    iOS与JS交互总结

    近几年来移动开发使用网页嵌入形式的越来越多,这就不可避免的出现原生控件和网页页面的JS交互,本篇就大概总结一下目前iOS开发中原生控件与JS的交互的几种形式。</br>

    iOS开发中使用的是UIWebView控件来加载网页页面资源的。所以我们也就主要围绕这个控件来总结一下,大体上可以分为三种形式:

    1、原生api交互,直接执行脚本,拦截代理
    2、第三方库WebViewJavascriptBrige交互,实质还是拦截代理
    3、JavaScriptCore框架
      3.1 context上下文,context上下文直接设置和调用
      3.2 JSExport协议,通过JSExport协议设置和调用


    接下来就一个一个的来看看到底是如何实现的,大家也可以提前把demo下载下来,结合代码来看效果会更好哦。

    原生api交互

    使用原生API来实现交互,实际上主要用到一个函数和一个协议,
    函数:

    - (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
    

    协议:

    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
    
    1、 OBJC调用JS

    直接使用UIWebView的函数执行JS代码,
    比如我们JS代码中有一个函数objcCallJS,

    function objcCallJS() {
            var data = 'ljt'
            alert('来自objc的调用,有返回值:' + data);
            return data
    }
    

    那我们该如何去调用呢?
    直接在相应的地方如此调用即可:

    NSString *returnStr = [self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJS()"];
        NSLog(@"返回值为:%@",returnStr);
    

    我们注意到这个地方会有个返回值,此时如果我们调用的JS代码有返回值得话,就会赋值到returnStr这个变量,可供我们后续使用。如果JS代码没有返回值得话,这个值默认是Undefine。
    OBJC调用JS代码就是如此的简单。这个时候,有人说了,如果我们想向JS代码中传值该怎么弄呢?
    JS代码:

    function objcCallJSParam(param1,param2) {
            alert('来自objc的调用,带有参数:' + param1 + '  ' + param2);
    }
    

    OBJC代码:

    - (void)callJSBtnParamAction:(id)sender {
        NSLog(@"开始调用JS函数,带有参数");
        [self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJSParam('ljt','ths')"];
    }
    

    这样我们就可以向JS代码中传入参数了。

    2、 JS调用OBJC

    在这里我们使用拦截UIWebView的代码来实现,我们知道代理函数- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;会在UIwebView开始加载页面的时候调用,如果我们返回NO则这个页面什么也不做,如果返回YES则加载这个页面。有了这个特性,我们就可以来实现JS调用OBJC的代码。
    </br>具体的流程: 页面响应时,重新设置页面的href,并将OBJC所用的参数以及所调用的指定函数,封装成一个特定格式的URL链接,然后我们在UIwebView的协议中拦截这个链接,然后解析出来,根据固定的格式做出相应的处理。
    具体来说:当我们页面中一个事件,设置了页面的href,携带了两个参数,

    //调用OBJC
    function JSCallObjc() {
        window.location.href="www.baidu.com/param=ljt&ths";
    }
    

    我们在OBJC中可以这么做:

    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
        NSLog(@"将要开始加载页面");
        NSString *urlStr = [[request URL] absoluteString];
        if ([urlStr containsString:@"param"]) {
            NSRange range = [urlStr rangeOfString:@"param="];
            NSString *paramStr = [urlStr substringFromIndex:range.location + range.length];
            NSLog(@"param=%@",paramStr);
            UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc弹框" message:paramStr preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
            [alterVC addAction:okAction];
            [self presentViewController:alterVC animated:YES completion:nil];
            return NO;
        }
        return YES;
    }
    

    这样就实现了JS调用OBJC的效果,当然这个例子不是那么好,href的格式我是随便写写,当真正使用起来的时候需要事先设计好合理的封装方式。
    </br></br>

    第三方库WebViewJavascriptBrige交互

    这个部分我们使用有名的第三方库WebViewJavascriptBrige来介绍一下,关于这个库的实现,网上已经有很多介绍,关于原理的实现就不多介绍了,直接进入使用环节,使用这个库的时候有一个地方需要注意一下,在所加载的页面中需要实现固定的写法:

    //固定函数,必须这样写
        function setupWebViewJavascriptBridge(callback) {
            if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
            if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
            window.WVJBCallbacks = [callback];
            var WVJBIframe = document.createElement('iframe');
            WVJBIframe.style.display = 'none';
            WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
            document.documentElement.appendChild(WVJBIframe);
            setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
        }
    
        //所有交互的函数都写在这里面
        setupWebViewJavascriptBridge(function(bridge) {
            ····
        })
    
    1、 OBJC调用JS

    首先我们需要在JS中注册一个函数,供OBJC来调用

    bridge.registerHandler('objcCallJS', function(data, responseCallback) {
                myAlert(data)
                var responseData = { 'Javascript Says':'Right back atcha!' }
                responseCallback(responseData)
    })
    

    这样注册之后,我们就可以在OBJC代码中调用这个名为objcCallJS的函数了。

    OBJC中的代码:

    - (void)callJSBtnAction:(id)sender {
        NSLog(@"调用JS函数");
        //callHandler有几种形式
        //- (void)callHandler:(NSString *)handlerName 只调用函数
        //- (void)callHandler:(NSString *)handlerName data:(id)data 调用的同时携带数据
        //- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback 不但调用和携带数据,而且设置回调函数处理所需的数据(如果需要处理结果数据)
    //    [self.bridge callHandler:@"objcCallJS" data:@{@"key":@"value"} responseCallback:^(id responseData){
    //        NSLog(@"%@",responseData);
    //    }];
    //    [self.bridge callHandler:@"objcCallJS"];
        [self.bridge callHandler:@"objcCallJS" data:@{@"key":@"value"}];
        
    }
    

    这个地方有几处我们需要说明的地方
    1、objcCallJS这个名称必须是一样的
    2、OBJC中调用JS的函数是通过- (void)callHandler:(NSString*)handlerName data:(id)data;这个借口调用的,这个接口的最终调用实现是- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;,当我们调用的时候,<strong>handlerName</strong>对应JS代码中注册的函数名,<strong>data</strong>对应JS代码中的data,即为OBJC向JS中传入的参数,还有一个有意思的参数就是<strong>responseCallback</strong>,就是JS代码的responseCallback,也就是说,当我们的JS代码处理完成之后,OBJC还有一次机会可以对JS代码处理完成的逻辑进行处理。当然data和responseCallback这两个参数都不是必须的。

    2、 JS调用OBJC

    与OBJC调用JS的逻辑类似,

    首先我们需要在OBJC中注册JS能够调用的函数:

    //注册js调用函数,并设定回调。js中可以调用JSCallObjc的函数
        [self.bridge registerHandler:@"JSCallObjc" handler:^(id data, WVJBResponseCallback responseCallback){
            NSString *paramStr = [data objectForKey:@"key"];
            UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc弹框" message:paramStr preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
            [alterVC addAction:okAction];
            [self presentViewController:alterVC animated:YES completion:nil];
            responseCallback(@"Response from testObjcCallback");
        }];
    

    注册完成以后,我们就可以在JS代码中调用这个注册好的函数了:

    //调用OBJC中的函数
            var callbackButton = document.getElementById('btn')
            callbackButton.onclick = function(e) {
                e.preventDefault()
                                     
                //callHandler的参数可变
                //bridge.callHandler('JSCallObjc') 没有携带数据
                //bridge.callHandler('JSCallObjc', {'key': 'value'}) 携带数据
                //bridge.callHandler('JSCallObjc', {'key': 'value'},function(response) {
                //      log('JS got response', response)
                //  }) 携带参数和回调函数
                       
                //bridge.callHandler('JSCallObjc')              
                bridge.callHandler('JSCallObjc',{'key': 'value'})
                
                
            }
    

    我们可以看到,这个逻辑和接口与OBJC调用JS的逻辑和接口大同小异,具体的参数也是差不多的。
    到此我们就可以利用这个第三方库来实现OBJC和JS的互相调用。其实这个库的内部还是通过拦截协议来实现这个交互过程的。

    JavaScriptCore框架

    JavaScriptCore框架是在iOS7之后引入的框架,这个框架在JS交互上为我们提供了很大帮助,可以在html界面上调用OC方法并传参,也可以在OC上调用JS方法并传参。关于JavaScriptCore的详细介绍和注意事项,请大家自行google之。这里我们只是简单介绍一下用法,很浅显,请大家不要见怪。
    使用之前,我们需要导入JavaScriptCore.framework这个框架

    context上下文,context上下文直接设置和调用
    1、 OBJC调用JS

    OBJC调用JS之前,需要获取一下JS的上下文,

    self.context = [self.homeWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
            NSLog(@"self.contex=%@",self.context);
    

    获取到这个上下文之后,我们就可以对JS的执行环境做处理了,假如我们需要调用JS中已经实现过的函数,我们可以直接调用相应的函数就行:

    - (void)callJSBtnAction:(id)sender {
        NSLog(@"开始调用JS函数,有返回值");
        //第一种
    //    NSString *returnStr = [self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJS()"];
    //    NSLog(@"返回值为:%@",returnStr);
        
        //第二种
    //    NSString *js = @"objcCallJS()";
    //    JSValue *value = [self.context evaluateScript:js];
    //    NSLog(@"%@",[value toString]);
        
        //第三种
        JSValue *valueFuc = self.context[@"objcCallJS"];
        JSValue *value = [valueFuc callWithArguments:nil];
        NSLog(@"%@",[value toString]);
    }
    

    以上代码中我们展示了三种不同的调用方式,特别说明的是第三种,首先我们从上下文中获取要执行的函数,并把它保存在一个JSValue变量中JSValue *valueFuc = self.context[@"objcCallJS"];,然后对这个变量调用其函数- (JSValue *)callWithArguments:(NSArray *)arguments;传入参数。
    我们可以在JS中这样使用:
    因为我们传入的参数为nil,所以我们在JS中没有获取参数,代码demo中我们提供了获取参数的版本,具体的可以参照代码demo,

    function objcCallJS() {
            var data = 'ljt'
            alert('来自objc的调用,有返回值:' + data);
            return data
     }
    
    2、 JS调用OBJC

    首先我们在OBJC中,增加JS可以执行的代码,怎么添加呢?还是需要用到上面所提到的上下文context
    加入我们需要实现一个可以接收参数的函数名为JSCallObjcParam,该怎么写呢?

    //有参数
        __weak typeof(self) weakSelf = self;
        self.context[@"JSCallObjcParam"] = (id)^(NSString *param1, NSString *param2) {
            NSLog(@"有参,无返回值");
            NSString *message = [NSString stringWithFormat:@"%@:%@",param1,param2];
            UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc弹框" message:message preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
            [alterVC addAction:okAction];
            [weakSelf presentViewController:alterVC animated:YES completion:nil];
        };
    

    这样我们就实现了一个可以在JS中调用的函数JSCallObjcParam,这个函数就收两个参数。
    我们在JS中这样调用:

    //调用OBJC
        function callObjcParam(param1,param2) {
            var data = JSCallObjcParam('ljt','ths')
            alert('来自objc(callObjcParam)的返回值:' + data);
        }
    

    这么两步我们就实现了iOS和JS的相互调用。</br>
    demo中我们分别实现了相互调用的传参和不传参,有返回值和没有返回值的情况,具体的可以看demo代码。

    JSExport协议,通过JSExport协议设置和调用

    JSExport是一个协议,我们可以自定义一个协议,继承此协议,在协议中声明可以在JS中使用的API函数。

    首先我们实现一个这么样的协议ObjcJSDelegate

    @protocol ObjcJSDelegate <JSExport>
    
    //有参数
    - (void)JSCallObjcParam:(NSString *)param1 with:(NSString *)param2;
    //无参数
    - (void)JSCallObjc;  
    //有返回值
    - (NSString *)JSCallObjcReturn;
    @end
    

    <strong>在这里我们需要知道的是,这个协议中所声明的接口是供JS调用的,也就是说JS代码调用OBJC的相关接口,可以放在这个协议中。而OBJC调用JS还是通过上下文来直接调用的</strong>

    其次,我们需要一个实现了这个代理的类,我们就直接实现在@interface JSExportViewController ()<ObjcJSDelegate>UIWebView所在的ViewController中:

    #pragma mark ObjcJSDelegate
    
    - (void)JSCallObjcParam:(NSString *)param1 with:(NSString *)param2 {
        NSLog(@"有参,无返回值");
        NSString *message = [NSString stringWithFormat:@"%@:%@",param1,param2];
        UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc弹框" message:message preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
        [alterVC addAction:okAction];
        [self presentViewController:alterVC animated:YES completion:nil];
    }
    
    - (void)JSCallObjc {
        NSLog(@"无参,无返回值");
    }
    
    - (NSString *)JSCallObjcReturn {
        NSLog(@"无参,有返回值");
        return @"ljt";
    }
    

    然后我们需要让JS知道如何调用我们所需要的函数,这个时候同样需要前面一直提到的上下文:

    self.context[@"OBJCFuc"] = self;
    

    如此,我们就可以在JS中通过OBJCFuc(OBJCFuc.接口名字)来调用我们之前协议实现的相关接口了:
    JS调用

    //调用OBJC
        function callObjcParam(param1,param2) {
            var data = OBJCFuc.JSCallObjcParamWith('ljt','ths')
            alert('来自objc(callObjcParam)的返回值:' + data);
        }
        function callObjc() {
            var data = OBJCFuc.JSCallObjc()
            alert('来自objc(callObjc)的返回值:' + data);
        }
        
        function callObjcReturn() {
            var data = OBJCFuc.JSCallObjcReturn()
            alert('来自objc(callObjcReturn)的返回值:' + data);
        }
    

    这样就实现了JS调用OJBC。


    这篇文章,只是简单的介绍了iOS和JS的交互的几种方法,很粗浅,很简单,但也许很实用,最后附上代码demo

    相关文章

      网友评论

        本文标题:iOS与JS交互总结

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