在进入主题之前,先简单了解一下主要所涉及的类
JSContext
JSValue
- 强引用JavaScript值(这些JavaScript值可以是JavaScript函数、JavaScript变量等)
- 绑定在一个
JSContext
上,绑定属于强引用
主题
- Objective-C调用JavaScript
- JavaScript调用Objective-C
- 内存管理
- 线程
- JavaScriptCore与WebView
1. Objective-C调用JavaScript
Objective-C调用JavaScript函数的基本步骤:
- 获取相关的JavaScript代码
- 通过
JSContext
的evaluateScript:
方法,装载JavaScript代码
- 通过
JSContext
的objectForKeyedSubscript:
方法,获取已装载的JavaScript函数,获取到的JavaScript函数也就是一个JSValue
对象
- 通过
JSValue
的callWithArguments:
方法,调用JavaScript函数
下面通过示例,进行演示。
function.js
文件中的内容如下,定义了一个函数,其名称为factorail
。
1 2 3 4 5 6 7 8 9
| var factorail = function(n) { if (n < 0) { return } if (n === 0) { return 1 } return n * factorail(n - 1) }
|
Objective-C调用JavaScript函数:
1 2 3 4 5 6 7 8 9 10 11
| // 获取`function.js`文件中的JavaScript代码 NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"functions" ofType:@"js"]; NSString *funcScript = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil]; // 创建一个JSContext JSContext *ctx = [JSContext new]; // 装载JavaScript代码 [ctx evaluateScript:funcScript]; // 获取`factorail`JavaScript函数 JSValue *jsFunctionValue = ctx[@"factorail"];// 等同于[ctx objectForKeyedSubscript:@"factorail"]; // 调用JavaScript函数,获取返回值 JSValue *jsReturnValue = [jsFunctionValue callWithArguments:@[@10]];
|
2. JavaScript调用Objective-C
有两种方式,可以实现JavaScript调用Objective-C。
- 2.1. 使用Block,实现JavaScript函数
- 2.2. 使用
JSExport
协议,实现JavaScript对象
2.1. 使用Block
具体说,就是通过JSContext
的setObject:forKeyedSubscript:
方法,为指定的下标设置一个Block,这样就相当于在JSContext
中装载了一个名称为下标名的JavaScript函数,这个函数的参数就是Block的参数,返回值类型就是Block的返回值类型。
在使用Block时,有两点需要注意:
- 避免捕获
JSValue
:应将其作为参数传递到Block中
- 避免捕获
JSContext
:应在Block中使用[JSContext currentContext]
示例,在JSContext
中装载一个名为makeUIColorWithNSDictionary
的JavaScript函数。
1 2 3 4 5 6 7 8 9 10 11
| #define RGB_COMPONENT_RED @"red" #define RGB_COMPONENT_GREEN @"green" #define RGB_COMPONENT_BLUE @"blue"
JSContext *ctx = [JSContext new]; ctx[@"makeUIColorWithNSDictionary"] = ^UIColor *(NSDictionary<NSString *, NSNumber *> *rgb) { float r = [rgb[RGB_COMPONENT_RED] floatValue]; float g = [rgb[RGB_COMPONENT_GREEN] floatValue]; float b = [rgb[RGB_COMPONENT_BLUE] floatValue]; return [UIColor colorWithRed:r green:g blue:b alpha:1.0]; };
|
装载后,就可以调用该JavaScript函数了,因为该JavaScript函数是通过Objective-C实现的,在其它JavaScript函数中调用该JavaScript函数,就可以实现JavaScript调用Objective-C。
接着,在function.js
文件中加入colorWithRGBDictionary(rgb)
JavaScript函数,这个函数,调用了上面已经为JSContext
设置的makeUIColorWithNSDictionary
。
1 2 3 4 5 6
| function colorWithRGBDictionary(rgb) { if (rgb) { return makeUIColorWithNSDictionary(rgb); } return undefined; }
|
其余的主要代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"functions" ofType:@"js"]; NSString *funcScript = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil];
[ctx evaluateScript:funcScript];
JSValue *jsFunctionValue = ctx[@"colorWithRGBDictionary"];
NSDictionary *rgb = @{ RGB_COMPONENT_RED: @(0.3), RGB_COMPONENT_GREEN: @(0.5), RGB_COMPONENT_BLUE: @(0.8), }; JSValue *colorValue = [jsFunctionValue callWithArguments:@[rgb]]; UIColor *color = [colorValue toObject];
|
2.2. 使用JSExport
协议
该方式的优点:很容易让JavaScript与Objective-C对象进行交互。
JSExport
协议的作用就是将Objective-C类、实例方法、类方法及属性暴露给JavaScript代码。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| @class MyPoint;
@protocol MyPointExport <NSObject, JSExport> @property (nonatomic, assign) double x; @property (nonatomic, assign) double y;
+ (MyPoint *)makePointWithX:(double)x y:(double)y; @end
@interface MyPoint : NSObject <MyPointExport> @property (nonatomic, assign) double x; @property (nonatomic, assign) double y;
+ (MyPoint *)makePointWithX:(double)x y:(double)y; - (double)distanceBetweenOriginalPoint; @end
@implementation MyPoint
+ (MyPoint *)makePointWithX:(double)x y:(double)y { MyPoint *p = [MyPoint new]; p.x = x; p.y = y; return p; }
- (NSString *)description { return [NSString stringWithFormat:@"<%@ - %p> : (x = %f, y = %f)", self.class, self, self.x, self.y]; }
- (double)distanceBetweenOriginalPoint { return sqrt(pow(self.x, 2) + pow(self.y, 2)); }
@end
|
在上面的代码中,自定义了一个MyPointExport
协议,该协议继承了JSExport
协议,在这个协议中,声明了两个属性和一个方法,因此,我们可以在JavaScript代码中使用它们。
在function.js
文件中加入middlePointOfTwoPoints(p1, p2)
JavaScript函数,在这个函数中,调用了协议中声明的属性及方法:
1 2 3 4 5
| function middlePointOfTwoPoints(p1, p2) { var x = (p1.x + p2.x) / 2.0; var y = (p1.y + p2.y) / 2.0; return Point.makePointWithXY(x, y); }
|
在上面的JavaScript代码中,使用了Point
类,它是与MyPoint
类相关联的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"functions" ofType:@"js"]; NSString *funcScript = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil];
JSContext *ctx = [JSContext new]; [ctx evaluateScript:funcScript];
JSValue *functionValue = ctx[@"middlePointOfTwoPoints"];
ctx[@"Point"] = [MyPoint class];// 在调用相关JavaScript函数之前,配置相关的类
MyPoint *p1 = [MyPoint makePointWithX:100 y:100]; MyPoint *p2 = [MyPoint makePointWithX:300 y:300];
JSValue *pointValue = [functionValue callWithArguments:@[p1, p2]]; MyPoint *middlePoint = [pointValue toObject];
|
注意MyPoint
类中的distanceBetweenOriginalPoint
方法,因为该方法并没有在MyPointExport
协议中进行声明,因此,在JavaScript代码中,是不能使用该方法的,否则就会产生异常。通过JSContext
的setExceptionHandler:
方法,可以捕获到异常:
1 2 3 4
| JSContext *ctx = [JSContext new]; [ctx setExceptionHandler:^(JSContext *context, JSValue *exception) { NSLog(@"exception: %@", exception); }];
|
3. 内存管理
- Objective-C使用ARC(你可能还在使用MRC)
- JavaScriptCore使用垃圾回收,所有的引用都是强引用
- JavaScriptCore API中的内存管理,多数是自动进行管理的
不过需要特别注意两种情形:
- 在Objective-C对象中存储JavaScript值
- 向Objective-C对象中添加JavaScript域
如何避免循环引用:
先使用JSManagedValue
对JavaScript值进行弱引用,然后再使用JSVirtualMachine
的addManagedReference:withOwner:
方法,将JSManagedValue
转入“垃圾回收”引用
4. 线程
JavaScriptCore API是线程安全的,锁粒度为JSVirtualMachine
,因此对于并发/并行,需要使用多个JSVirtualMachine
。
单个进程可以包含多个JSVirtualMachine
,单个JSVirtualMachine
可以包含多个JSContext
,单个JSContext
可以包含多个JSValue
。
处于同一个JSVirtualMachine
中的JSValue
,可以在该JSVirtualMachine
中的JSContext
之间进行传递,但不能传递到其它JSVirtualMachine
的JSContext
中。
5. JavaScript与WebView
要想通过JavaScriptCore
框架让web view中的JavaScript与Objective-C进行交互,关键点就是获取web view中的JSContext
。
获取到JSContext
后,就可以装载自定义对象,以及替换原有的回调函数。
对于WebView
类,通过WebFrameLoadDelegate
协议的webView:didCreateJavaScriptContext:forFrame:
方法,可以获取到JSContext
对象。
但对于UIWebView
类,苹果官方并没有公开相关的方法。不过,可以使用下面的两种方式:
1
| JSContext *ctx = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
|