iOS与JS交互 JSCore

源起

随着互联网移动开发的热潮,web开发近几年也出现了许多移动开发框架,比如Rect native等许多,但是还是有一定的局限性,比如需要做一个端上 H5 照片上传功能,通过 JS 去实现往往效果会大打折扣,也很难达到业务方需要的顺滑体验,所以就出现了移动端和前端的交互。
作为一名iOS开发者,讲讲iOS 与JS的交互。怎么做呢?在他们之间建立一座桥梁就很OK,感觉好多交互都是建立“桥”,比如Swift与OC的相互调用就会建立一个桥接文件。编程之道,博大精深。
苹果针对这个就封装出一个JavaScriptCore框架,简称JSCore。

JSCore

大家都知道浏览器内核的模块主要是由渲染引擎和 JS 引擎组成,其中 JSCore 就是一种 JS 引擎
Apple 通过将 WebKit 的 JS 引擎用 OC 封装,提供了一套 JS 运行环境以及 Native 与 JS 数据类型之间的转换桥梁,常用于 OC 和 JS 代码之间的相互调用,这也意味着他可以脱离渲染单独去执行 JS。
iOS与JS交互 JSCore_第1张图片

  • 运行
    iOS与JS交互 JSCore_第2张图片
    从上图我们可以看到一个这样的过程:
    在 Native 应用中我们可以开启多个线程来异步执行我们不同的需求,也就意味着我们可创建多个 JSVirtualMachine 虚拟机(运行资源提供者),同时相互隔离不影响,这样我们就可以并行地执行不同 JS 任务。
    在一个 JSVirtualMachine 中还可以关联多个 JSContext (JS 执行环境上下文),并通过 JSValue(值对象) 来和 Native 进行数据传递通信,同时可以通过 JSExport (协议) ,将 Native 中遵守此解析的类的方法和属性转换为 JS 的接口供其调用。

JSContext

简单的理解为执行JavaScript的一个环境

JSValue

可以理解成一种供iOS数据结构与JS数据结构相互转换的包装,也可以看成一种桥接关系,我们执行JS获取的结果就是通过JSValue对象进行包装传给客户端进行处理的,类型转换官方文档描述如下:

iOS与JS交互 JSCore_第3张图片
以上图来自知乎专栏
下面来简单看一下两个如何使用

 JSContext *jsContext = [[JSContext alloc] init];
    [jsContext evaluateScript:@"var num = 500"];
    [jsContext evaluateScript:@"var computePrice = function(value){return value * 2}"];
    JSValue *value = [jsContext evaluateScript:@"computePrice(num)"];
    int intVal = [value toInt32];
    NSLog(@"计算结果为%d", intVal);//结果为1000

JSExport

这是一个协议,官方文档没有暴露出任何的open协议方法,可以理解为一个空协议。
通常用法是自定义一个CustomExport : JSExport,里面将JS可以调用的属性或者方法进行暴露,JS就可以直接使用暴露的属性与方法了。
ObjC方法定义样式是非常特殊的,但官方文档给出了转换后JS调用的样式:

//Objective-C
- (void)doFoo:(id)foo withBar:(id)bar;

//JS
doFooWithBar(foo,bar)

但这样会有一个缺点,万一,方法有很多个参,拼接起来的JS方法名简直就是日了X;不过这点Apple已经帮我们想到了,使用JSExportAs宏,可以将方法名简化,就像Swift中的typealias以及ObjC中的typedef。

//这样在JS中直接调用doFoo(foo,bar)即可
 JSExportAs(doFoo,
  - (void)doFoo:(id)foo withBar:(id)bar
  );

关于WKWebView的详尽总结可以看iOS WKWebView的使用

通过 JSExport 暴露 iOS 方法属性给 JS

这个特性可能 H5 的同学不是很清楚,但是对于 Native 同学,我认为非常有用。

通过 JSExport 可以很方便地将 iOS 对象的属性方法暴露给 JS 环境,让其使用起来像 JS 对象一样方便。

比如我们 OC 中有一个 Person 的类,包含两个属性和一个方法,此处通过让fullName方法使用 JSExport 协议暴露出去,这样在 JS 中是可以直接去调用的。

@protocol PersonProtocol 
- (NSString *)fullName;
@end

@interface Person : NSObject 
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@end

@implementation Person
@synthesize firstName, lastName;
(NSString *)fullName {
  return [NSString stringWithFormat:@"%@ %@",self.firstName, self.lastName];
}
@end
// 通过 JSExport 暴露 iOS 方法属性给 JS
Person *person = [[Person alloc] init];
jsContext[@"p"] = person;
person.firstName = @"Fei";
person.lastName = @"Zhu";
NSLog(@"通过 JSExport 暴露 iOS 方法属性给 JS--------");
[jsContext evaluateScript:@"log(p.fullName());"];
[jsContext evaluateScript:@"log(p.firstName);"];

最终运行结果为:

2018-03-16 20:51:17.437688+0800 JSCoreDemo[4970:219193] JSExport 暴露 iOS 方法属性给 JS========
2018-03-16 20:51:17.438100+0800 JSCoreDemo[4970:219193] Fei Zhu
2018-03-16 20:51:17.438388+0800 JSCoreDemo[4970:219193] undefined

为什么p.firstName运行后是undefined呢? 因为在这里没有将其暴露到 Native 环境中,所以就获取不到了。

这里我们可以利用的更多的是在编程便捷性上面,让 OC 和 JS 直接可以相互调用。

OC调用JS

用过的话就一定知道WKWebView显示网页字体变小,那么这时候就可以调用JS方法来设置

采用evaluateJavaScript执行JS脚本即可改变,当然你还可以设置许多属性

// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
- 
    [webView evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '300%'" completionHandler:nil];
    [webView evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextFillColor= '#9098b8'" completionHandler:nil];
 
    
}

如果想要调用JS的方法的话,依旧可以写成一个字符串

NSString *jsString = [NSString stringWithFormat:@"changeColor('%@')", @"Js参数"];
    [_webView evaluateJavaScript:jsString completionHandler:^(id _Nullable data, NSError * _Nullable error) {
        NSLog(@"改变HTML背景颜色");
    }];

只是简单的讲了如何交互,还有许多功能仍需探索

你可能感兴趣的:(iOS与JS交互 JSCore)