引言
鉴于很多小伙伴最近老是问我关于js与OC交互的问题,原本打算先写完CoreAnimation的我,决定先吧这块知识点整理出来,毕竟核心动画的知识的确比较多,不是那么容易写完。
#OC与js的交互方式
OC与js的交互方式有好几种,不借助第三方框架的情况下,我们有两个选择,webkit和javascriptCore。
##方法一:通过webkit进行OC与js的交互
没认识JavaScriptCore之前,如果想在OC中使用JavaScript代码,一般都是在webview(webview内置webkit引擎,解析JavaScript代码)
主要方法就是在webView的代理中执行:stringByEvaluatingJavaScriptFromString:@"JS的方法名"
这个方法,即可在webView中调用js的函数
同时,通过代理方法webView:shouldStartLoadWithRequest: navigationType:
监听由js在内部定义的类似重定向的消息,并在OC中执行相关的代码
代码如下:
js的实现如下:
这种方式可以解决大多数不需要太过复杂的OC与js交互的需求,但对于比较复杂,或者OC与js交互频繁的情况下,更推荐大家使用方法二。
##方法二:通过javascriptCore进行OC与js的交互
###javascriptCore简介
这是javascriptCore的头文件
在头文件中,我们可以看到,javascriptCore中有5个类,分别是
JSVirtualMachine
JSVirtualMachine顾名思义,是javaScript的虚拟机,是为JSContext提供运行资源。JSManagedValue
主要是作为一个引用桥接,将 JSValue 转为 JSManagedValue 类型后,可以添加到 JSVirtualMachine 对象中,这样能够保证你在使用过程中 JSValue 对象不会被释放掉,当你不再需要该 JSValue 对象后,从 JSVirtualMachine 中移除该 JSManagedValue 对象,JSValue 对象就会被释放并置空。JSContext
JSVirtualMachine为JavaScript的运行提供了底层资源,JSContext就为其提供着运行环境。通过- (JSValue )evaluateScript:(NSString )script;方法就可以执行一段JavaScript脚本,并且如果其中有方法、变量等信息都会被存储在其中以便在需要的时候使用。而JSContext的创建都是基于JSVirtualMachine:- (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;,如果是使用- (id)init;进行初始化,那么在其内部会自动创建一个新的JSVirtualMachine对象然后调用前边的初始化方法。JSValue
JSValue则可以说是JavaScript和Object-C之间互换的桥梁,它提供了多种方法可以方便地把JavaScript数据类型转换成Objective-C,或者是转换过去。JSExport
JSExport是一个协议,让JSContext运行环境中的JavaScript 可以识别该协议中定义的实例方法、类方法、属性等,让objective-c/swift与JavaScript能够自动交互;
###通过OC执行js方法或调取js属性。
下面通过JSContext的evaluateScript方法模拟执行js代码,写一些简单的demo。
例子:
示例代码里的,JSValue *jsArray,jsArray对应着javaScript中的一个 array对象:arr。所以我们可以对jsArray进行一些操作,从而操作javaScript 中的 “arr”。
例如:
jsArray[0];//1
jsArray[2];//This is js string
jsArray[1] = 49;//修改arr 的第二个元素。
jsArray[@”length”];//结果是3,调用js arr对象的方法。
又比如我们示例代码里的,jsSum,对应着sum function,因此我们可以通过操作jsSum从而调取javaScript中的 “sum function”。
如同这个例子,我们可以方便的通过JSValue对象的 callWithArguments:方法来直接调取 js 的 function。js function的多参数,在OC中,由NSArray组装而成。
###通过js执行OC方法或调取OC属性。
####有两种方式可以方便的通过js 调用 OC:
- Block 用来调用方法。
我们有一个OC方法,提供给js调用1234- (NSInteger)sumWithA:(NSInteger)a B:(NSInteger)b C:(NSInteger)c{return a + b + c;}
- (void)jsToOcFunction
{
//需要写的代码
JSContext *context = [[JSContext alloc] init];
context[@”sumNums”] = ^(NSInteger a, NSInteger b, NSInteger c) {
};return [self sumWithA:a B:b C:c];
//模拟执行js代码
JSValue *sum = [context evaluateScript:@”sumNums(7, 56, 22)”];
NSLog(@”sum %@”, sum);//sum 85
}1234Block方式需要特别注意的是:1. 不论在任何情况下,不要在Block中直接使用外面的JSValue对象, 而应该把JSValue当做参数来传进Block中。2. 不论在任何情况下,不要在Block中直接使用外面的JSContext对象, 而应该使用 [JSContext currentContext]获取。
- JSExport protocol 用来调用对象。
使用rumtime 为一个系统控件UIButton增加JSExport protocol1@protocol UIButtonExport <JSExport>
(void)setTitle:(NSString *)title forState:(UIControlState)state;
@end(void)changeTitle
{
class_addProtocol([UIButton class], @protocol(UIButtonExport));UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button setTitle:@”你好 OC” forState:UIControlStateNormal];
button.frame = CGRectMake(100, 100, 100, 100);
[self.view addSubview:button];JSContext *context = [[JSContext alloc] init];
context[@”button”] = button;
[context evaluateScript:@”button.setTitleForState(‘你好 js’, 0)”];
}1通过runtime的方式增加JSExport protocol之外,还可以通过category的方式,比如:
|
|
可以看到,如果想要在js中调用OC 的类或者对象的方法,需要将方法在JSExport protocol中声明。
当然上面这些只是一些演练,我们在实际操作中会有些不同。
javascriptCore 实战
在最近的斑马王国2.0的app中,支付中有优惠券的使用,而优惠券是放在H5进行实现的,有个简单的OC与js的交互。
当用户点击app中的优惠券时,打开优惠券的网页,在优惠券页面中点击对应的优惠券,则将对应的优惠券选中,传回app并跳转回支付页面。
在这次的开发中,我使用了block的方式进行了实现。实现过程中,我发现JSContext的这个上下文似乎应该从页面获取,经过打印webView的相关信息,发现我们可以通过这样的方式获取javascriptContext:[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
下面是在优惠券的h5页面声明OC方法的代码:
我在网页加载成功的时候,来获取这个准确的JSContext,同时声明了我们将要在JS中调用的OC方法,并进行了异常信息的打印。
下面是OC中执行的优惠券选择代码:
而在JS中,我们只需要在选中优惠券的时候,selectCoupons(couponsName,excCode,select);
这样调用即可。
##总结
通过javascriptCore,我们可以随意的在JS与OC中进行切换。
1、在OC/swift里,所有JavaScript代码都可以在JavaScript运行环境(JSContext)中通过evaluateScript运行;
2、在OC/swift里,所有JavaScript中的方法、对象、属性都可以通过objectForKeyedSubscript(类似字典的方式,context.objectForKeyedSubscript("Person") == context[@"Person"]
,上面的演示都用的是后者)来取得,取得所有对象均为JSValue类型
3、通过objectForKeyedSubscript取得的JavaScript中的对象,都遵循该对象在JavaScript中有的所有特性,如数组的长度,无数组越界,自动延展的特性
4、通过objectForKeyedSubscript取得的JavaScript中的方法,均可以通过callWithArguments传入参数调用JavaScript中的方法并返回正确的结果(类型仍然为JSValue,可以通过toObject的方式转化为对应的OC对象)
5、补充一点:除了通过objectForKeyedSubscript取得JavaScript对象外,我们也可以通过 setObjectForKeyedSubscript的方式给JavaScript中传递类型或对象(传递类型:context[@”Book”] = [Book class];传递对象:context[@”book”] = book),传递完成的对象或者类型,可以在JavaScript中直接使用,类似button.setTitleForState('你好 js', 0)
6、我们可以通过继承JSExport的中自定义协议(@protocol)(定义的属性、方法)的方式,让给任意类型添加可以在JavaScript中访问到的属性、方法,方便JS直接进行点操作
本作品采用 署名-非商业性使用-相同方式共享 2.5 中国大陆 (CC BY-NC-SA 2.5)协议
进行许可,欢迎转载,但转载请注明来自SarielTang
,并保持转载后文章内容的完整。本人保留所有版权相关权利。
本文永久链接:http://sarieltang.github.io/2015/12/25/总结分析/2015-12-25/index/