鱼乐DevSupport我爱编程

iOS与JS交互总结

2017-05-02  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读