美文网首页
iOS之OC与JS交互(UIWebView)

iOS之OC与JS交互(UIWebView)

作者: 枫叶无处漂泊 | 来源:发表于2019-05-26 19:26 被阅读0次

前言

最近项目开发中用到了OC与JS交互方面的知识,以前也用过UIWebView JS与OC交互方面的,使用的苹果在iOS7开放的javascriptCore框架,使用起来挺方便快捷,javascriptCore源码是开放的,有兴趣的可以去了解一下。

自从iOS8,苹果就UIWebView性能不好,推出了WKWebView,以及github上评分很高的WebViewJavascriptBridge里面最新版本也最WKWebView做了兼容。

实现的方式

所以我总结的方式,分UIWebview、WKWebView、以及通用版本的第三方WebViewJavascriptBridge,进行实现。

  • UIWebView中JS与OC交互
  • WKWebView中JS与OC交互(只能iOS8及之后的版本)
  • WebViewJavascriptBridge对UIWebView与WKWebView做了同意处理。

UIWebView与JS交互

与JS交互需要了解的5个类,JavaScriptCore框架包含的5个类

  • JSContext

    • JSContext是JavaScript的运行环境,他主要作用是执行JS代码和注册OC方法接口,相当于HTML中< JavaScript ></JavaScript >之间的内容。
  • JSValue

    • JSValue是JSContext的返回结果,他对数据类型进行了封装,并且为JS和OC的数据类型之间的转换提供了方法。
  • JSManagedValue

    • JSManagedValue是JSValue的封装,用它可以解决JS和原生代码之间循环引用的问题
  • JSVirtualMachine

    • JSVirtualMachine 是管理JS运行时和管理JS暴露的OC对象的内存。
  • JSExport

    • JSExport是一个协议,通过实现它可以把一个OC对象暴漏给JS,这样JS就可以调用这个对象暴露的方法

使用

  • 首先要是设置<UIWebViewDelegate>代理,代理实现的方法:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {

    //在加载网页之前,是否加载网页。YES加载 NO 不加载
    return YES;
}
- (void)webViewDidStartLoad:(UIWebView *)webView {

    //开始加载网页
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    //webveiw加载完成
}

  • 初始化UIWebview
//JavaScriptCore导入这个类
#import <JavaScriptCore/JavaScriptCore.h>

//这个是加载URL
// [self.webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"]]];
    
//加载本地的html
NSString *path = [[[NSBundle mainBundle] bundlePath]  stringByAppendingPathComponent:@"test.html"];

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]];
self.webview.delegate = self;
[self.webview loadRequest:request];
    
  • 在webview在家完成时,获取标题JSContext
self.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
   
//JSContext是JavaScript的运行环境,建立js与oc的上下文,相当于打通oc与js交互的桥梁  
JSContext *context = [self.webview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

//oc调用js方法
[self OCToJS:context];

//js调用OC的方法
[self JSCallOC:context];

//通过JSExport可以调用OC对象的方法
[self JSuSEJSExportToOC:context];

  • 先展示一下test.html的<script></script>代码
<script type="text/javascript">
                
    //responseData数据就是OC传来的,字典、数组、字符串都会相应的转成js的数据类型
   function getUserIdFromOC(responseData) {
       
      //传过来的参数js做处理           
      log(responseData.userId);
   }
    
 //多个参数       
   function getParamsFromOC(responseData1,responseData2) {
    //传过来的参数js做处理                 
    log(responseData1.userId + responseData2);
}
            
</script>

  • OC调用JS方法
- (void)OCToJS:(JSContext *)context {
    
    //相当于获取JS的getUserIdFromOC方法
    JSValue *result = context[@"getUserIdFromOC"];
    
    NSDictionary *dict = @{@"userId": @"123456"};
    
    //传的参数是数组。数组里面可以放字典、数组、字符串等
    [result callWithArguments:@[dict]];
    
    //执行异常的报错
    context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
    
        NSLog(@"exception -- %@",exception);
    };
}

可以打印一下 result,如下结果:

//打印出来是JS的方法,callWithArguments相当于调用了getUserIdFromOC把值传进去
getUserIdFromOC(responseData) {
                    
    log(responseData.userId);
}

运行一下执行结果和前端配合一下查看有没有值,或者使用 alert(responseData.userId);自行弹出看看有没有值如下图:

OC调用JS.png

JS调用OC方法

  • 直接调用OC方法

在test.html的<body></body>中添加几个button,代码如下:


<br/>
<input type="button" value="给OC传多个参数" 
onclick="getParamsFromJS('张三','18');" />
<br/>
                
<input type="button" value="给OC传数组" 
onclick="getArrayFromJS(['11111','22222']);" />
<br/>
                
<input type="button" value="给OC传字典" 
onclick="getDictFromJS({'one':'1','two':'2'});" />
<br/>
                
<input type="button" value="调用oc然后回到js的方法" 
onclick="getDataJSAndReturnJS('111');" />
<br/>

执行如下方法:


- (void)JSCallOC:(JSContext *)context {

    //js方法传几个参数jm,你就接受几个参数,参数类型和OC相对应
    context[@"getParamsFromJS"] = ^(NSString *str,NSString * str2){
      //  __strong typeof(self) strongSelf = self;
        NSLog(@"context -- %@ context -- %@",str,str2);
    };
    
    //JS传输组
    context[@"getArrayFromJS"] = ^(NSArray *array) {
        
        NSLog(@"array -- %@",array);
    };
    
    //JS传字典
    context[@"getDictFromJS"] = ^(NSDictionary *dict) {
    
        NSLog(@"dict - %@",dict);
    };
    
    //js调用oc oc再调用js里方法
    //避免循环引用
    __block typeof(context) weakContext = context;
    context[@"getDataJSAndReturnJS"] = ^(NSDictionary *dict) {
        
        NSLog(@"dict - %@",dict);
        //可以再把js传来的值传到js方法中
        [weakContext[@"getUserIdFromOC"] callWithArguments:@[dict]];
    };
}

执行结果如图:

JS调用OC.png

分别点击每个按钮,查看一下结果:

str -- 张三 str2 -- 18

array -- (
    11111,
    22222
)

dict - {
    one = 1;
    two = 2;
}

dict - {
    userId = 123456;

  • 使用JSExport将OC对象暴漏给JS

JSExport是JavaScriptCore框架里的一个协议。如果一个协议遵守了JSExport,那么该协议的方法会对JS开放,允许JS直接调用。


//只要遵守NativeJSExport协议,
//该协议的方法会对JS开放,允许JS直接调用
@protocol NativeJSExport <JSExport>

//多个参数的话,参数方法名拼接起来是js调用的方法名
//testMethodWithParam1Param2就是js的方法
- (void)testMethodWithParam1:(NSString *)str1 Param2:(NSString*)str2;

- (void)test:(NSInteger)number Method:(NSString*)str;

- (void)testArray:(NSArray*)array;

- (void)testStr:(NSString*)string;

- (void)testDict:(NSDictionary*)dict;

@end

使用JSExport来让js调用oc方法

- (void)JSUseJSExportCallOC:(JSContext *)context {

    //JSExport协议关联native的方法,要在webView的delegate里面添加
    /*
     会有循环引用问题,导致self的-dealloc方法不被执行。
     因为JS中没有弱引用,所以__weak在这里不起作用。
     一般来说,可以使用单独的类来处理OC JSExport协议的相关方法,以解决此问题
     */
//    context[@"Native"] = self;
    
    //所以要实现JSExport,单独写一个类,遵守NativeJSExport
    context[@"Native"] = [Native new];
    // 打印异常
    context.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
        context.exception = exceptionValue;
        NSLog(@"%@", exceptionValue);
    };
}

为了让大家能更好理解JSExport,把Native这个对象代码写下来:

Native.h的源码:

#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

@protocol NativeJSExport <JSExport>

//多个参数的话,参数名字拼接起来是js调用的方法名
- (void)testMethodWithParam1:(NSString *)str1 Param2:(NSString*)str2;

//testMethod就是js的方法名
- (void)test:(NSInteger)number Method:(NSString*)str;

- (void)testArray:(NSArray*)array;

- (void)testStr:(NSString*)string;

- (void)testDict:(NSDictionary*)dict;

@end

//让这个Navtive对象遵守该协议
//只要遵守NativeJSExport协议,
//该协议的方法会对JS开放,允许JS直接调用
@interface Native : NSObject<NativeJSExport>

@end

Native.m

#pragma mark -- JSExport 协议 --
- (void)testArray:(NSArray*)array {
    
    NSLog(@"array -- %@",array);
}

- (void)testStr:(NSString*)string {
    
    NSLog(@"str -- %@",string);
}

- (void)testDict:(NSDictionary*)dict {
    
    NSLog(@"dict -- %@",dict);
}

//数组的类型会通过JSValue把JS数据类型转换成OC
- (void)testMethodWithParam1:(NSString *)str1 Param2:(NSString*)str2 {
    
    NSLog(@"str1 - %@ str2 - %@",str1,str2);
}

- (void)test:(NSInteger)number Method:(NSString*)str {
    
    NSLog(@"number --%ld, str -- %@",(long)number,str);
}

@end

执行结果:


str1 - 张三 str2 - 18

number --18, str -- 22222

array -- (
    11111,
    22222
)
 
str -- 测试

dict -- {
    one = 1;
    two = 2;
}

总结

OC调用JS

  • 通过JSContxt获取JS的代码
  • context[@"js方法名"]获取返回给JSValue
  • 通过JSValue的 callWithArguments:把参数传进去执行JS方法

JS调用OC

  • block形式

    • 如:context[@"js方法"] = ^(id str,id str2)
    • 参数可以是多个,数据类型可以是NSArray、NSdictionary、NSString、NSInter等
  • JSExport形式

    • JSExport是JavaScriptCore框架里的一个协议。如果一个协议遵守了JSExport,那么该协议的方法会对JS开放,允许JS直接调用。
    • 创建一个对象,改对象遵守JSExport协议
    • 调用context[@"Native"] = [对象 new];
    • Native就是将JS中Native.方法名同意交给遵守该JSExport协议的对象去做。

相关文章

网友评论

      本文标题:iOS之OC与JS交互(UIWebView)

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