OC与JS交互(一) ~~~~~ JavaScriptCore
前言
前段时间做app活动,发现与JS交互方面有些混乱,为了梳理交互操作,又重新看了一遍原生与JS交互方面的东西
同时找到了下面参考文档中的深入浅出和全面解析写的还不错,多余的就不赘述了,自己总结一下,不想看的看官门可以跳过【个人总结】这一部分
个人总结
- JavaScriptCore是iOS7.0+ macOS10.9+
- JavaScriptCore中包括四个类:JSVirtualMachine、JSContext、JSManagedValue、JSValue
- JSVirtualMachine实例表示JavaScript执行的独立环境,使用这个类有两个主要目的:支持并发JavaScript执行,以及管理在JavaScript和Objective-C或Swift之间桥接的对象的内存
- JSVirtualMachine实例可以包括多个JSContext,JSContext之间可以传值(JSValue对象),JSVirtualMachine之间不能传值
- 想要并发执行JavaScript,需要为每个线程创建单独的JSVirtualMachine实例
- 使用JSManagedValue来管理JavaScript值,并将被管理值的所有权交给JavaScriptCore virtual machine
- JSManagedValue是对JSValue的包装,加入了“有条件保留”的行为,从而达到自动管理的目的
- JavaScript值可以通过JavaScript对象访问(既不受JavaScript回收机制影响)
- JsManegedValue可以通过OC或Swift进行访问
- 可以理解为JSManagedValue是对JSValue的弱引用
交互操作
首先要声明获取JSContext对象,有下面三个方式,三选一
- (void)webViewDidFinishLoad:(UIWebView *)webView { // 1.这种方式需要传入一个JSVirtualMachine对象,如果传nil,会导致应用崩溃的。 JSVirtualMachine *JSVM = [[JSVirtualMachine alloc] init]; JSContext *JSCtx = [[JSContext alloc] initWithVirtualMachine:JSVM]; // 2.这种方式,内部会自动创建一个JSVirtualMachine对象,可以通过 JSCtx.virtualMachine // 看其是否创建了一个JSVirtualMachine对象。 JSContext *JSCtx = [[JSContext alloc] init]; // 3. 通过webView的获取JSContext。 JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; }
JSContext对象对应一个全局对象(global object)。例如web浏览器中的JSContext,起全局对象就是window对象。全局变量是全局对象的属性,可以通过JSValue对象或者context下标的方式来访问。
所以感觉用方法3,去获取而不是创建更好一些。
JavaScript 与 Objective-C 交互主要通过2种方式:
block方式:使用block将响应方法暴露给JavaScript,从而完成Objective-C的一些操作
JSExport 协议:通过继承JSExport协议的方式来导出指定的方法和属性,可以将OC的中某个对象直接暴露给JS使用,而且在JS中使用就像调用JS的对象一样自然。
block方式
OC变量 <---> JS变量
//执行的JS操作 为a赋值 JSValue *value = [context evaluateScript:@"var a = 1+2*3;"]; //获取a的值的三种方法 NSLog(@"a = %@", [context objectForKeyedSubscript:@"a"]);//通过context的实力方法objectForKeyedSubscript NSLog(@"a = %@", [context.globalObject objectForKeyedSubscript:@"a"]);//通过context.globalObject的ObjectForKeyedSubscript实例方法 NSLog(@"a = %@", context[@"a"]); //通过下标 //输出结果Output: a = 7 a = 7 a = 7
JS方法 ---> OC方法
JS方法
//点击调用shareClick方法 <input type="button" value="分享" onclick="shareClick()" /> //shareClick方法 function shareClick() { //调用原生share方法 //调用方法不带参数 share(); //调用方法带参数 share('测试分享的标题','测试分享的内容','url=http://www.baidu.com'); }
OC方法
- (void)addShareWithContext:(JSContext *)context { context[@"share"] = ^() { //从JavaScript代码传过来的参数 NSArray *args = [JSContext currentArguments]; //参数的一些处理 if (args.count < 3) { return ; } NSString *title = [args[0] toString]; NSString *content = [args[1] toString]; NSString *url = [args[2] toString];
// 在这里执行分享的操作
}; }
OC方法 ---> JS方法
JS代码
function shareResult(channel_id,share_channel,share_url) { //拼接数据 var content = channel_id+","+share_channel+","+share_url; asyncAlert(content); //修改当前returnValue的值 document.getElementById("returnValue").value = content; }
OC代码
- (void)addShareWithContext:(JSContext *)context { context[@"share"] = ^() { NSArray *args = [JSContext currentArguments]; if (args.count < 3) { return ; } NSString *title = [args[0] toString]; NSString *content = [args[1] toString]; NSString *url = [args[2] toString]; // 在这里执行分享的操作 // 将分享结果返回给js NSString *jsStr = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url]; [[JSContext currentContext] evaluateScript:jsStr]; }; }
这样的话也完成了JS的回调,既调用JS中的方法处理返回值
JSExport 协议
简单的说就是写一个继承JSExport的协议,写一个类遵循该协议,将这个类作为一个变量交给JavaScriptCore。这样JavaScriptCore会自动认定继承JSExport这个类的协议为要导入到JavaScript的方法和属性列表,从而进行导入。
代码1显示了协议中的变量,方法和类方法。代码2显示了对应的JavaScript中调用的方法
代码1
@protocol MyPointExports <JSExport> //对于每个导出的Objective-C属性,JavaScriptCore都会在原型上创建JavaScript访问器属性。 @property double x; @property double y; //对于每个导出的实例方法,JavaScriptCore都会创建一个相应的JavaScript函数作为原型对象的属性。 - (NSString *)description; - (instancetype)initWithX:(double)x y:(double)y; //对于每个导出的类方法,JavaScriptCore在构造函数对象上创建一个JavaScript函数 + (MyPoint *)makePointWithX:(double)x y:(double)y; @end
代码2
// Objective-C properties become fields. point.x; point.x = 10; // Objective-C instance methods become functions. point.description(); // Objective-C initializers can be called with constructor syntax. var p = MyPoint(1, 2); // Objective-C class methods become functions on the constructor object. var q = MyPoint.makePointWithXY(0, 0);
其中MyPoint为遵循MyPointExports的类
你可能注意到了,后两个方法参数写法上似乎有些变化。
如上面英文提到的,初始化方法可以用构造函数方法调用
而多参数问题,导入到JavaScript时由这么一个规则
-
- 所有冒号都从方法选择器中删除
- 任何后面冒号的小写字母都变成大写
例如:OC中为 doFoo:withBar:
JavaScript中为 doFooWithBar
不好记? 苹果又贴心的给了一个宏来指定导出到Javascript的方法名
@protocol MyClassJavaScriptMethods <JSExport> JSExportAs(doFoo, - (void)doFoo:(id)foo withBar:(id)bar ); @end
到此,用法方面就结束了
后记
其实如果你看了下面的参考文档,你就会觉得他们确实写的很全面,我再写这一篇的时候也这么觉得,同样也犹豫过,在很显然不如别人写的好的情况下要不要在写下去。
后来想清了一个问题,虽说不要重复造轮子,但是不会造轮子的话,永远只能当组装师搬运工,学会造轮子之后才能成为创造者。一千个读者就有一千个哈姆雷特,或许你的切入点、视角更好呢,就算不是完美,至少你走过了这一步,也印象更深刻了些。虽然不完美,但还是写完了,希望能够对你有所帮助。
PS:如果你看了官方文档,会发现,大多数信息源还是官方文档~~~
参考文档
iOS UIWebView与JavaScript交互之JavaScriptCore
iOS下JS与OC互相调用(四)--JavaScriptCore