随着互联网移动开发的热潮,web开发近几年也出现了许多移动开发框架,比如Rect native等许多,但是还是有一定的局限性,比如需要做一个端上 H5 照片上传功能,通过 JS 去实现往往效果会大打折扣,也很难达到业务方需要的顺滑体验,所以就出现了移动端和前端的交互。
作为一名iOS开发者,讲讲iOS 与JS的交互。怎么做呢?在他们之间建立一座桥梁就很OK,感觉好多交互都是建立“桥”,比如Swift与OC的相互调用就会建立一个桥接文件。编程之道,博大精深。
苹果针对这个就封装出一个JavaScriptCore框架,简称JSCore。
大家都知道浏览器内核的模块主要是由渲染引擎和 JS 引擎组成,其中 JSCore 就是一种 JS 引擎
Apple 通过将 WebKit 的 JS 引擎用 OC 封装,提供了一套 JS 运行环境以及 Native 与 JS 数据类型之间的转换桥梁,常用于 OC 和 JS 代码之间的相互调用,这也意味着他可以脱离渲染单独去执行 JS。
简单的理解为执行JavaScript的一个环境
可以理解成一种供iOS数据结构与JS数据结构相互转换的包装,也可以看成一种桥接关系,我们执行JS获取的结果就是通过JSValue对象进行包装传给客户端进行处理的,类型转换官方文档描述如下:
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
这是一个协议,官方文档没有暴露出任何的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的使用
这个特性可能 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 直接可以相互调用。
用过的话就一定知道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背景颜色");
}];
只是简单的讲了如何交互,还有许多功能仍需探索