來源:http://nscoder.me
还记得以前在网页上用 iframe
指定一个 URL 并通过 native 的 - webView:shouldStartLoadWithRequest:navigationType:
回调方法拦截请求,再蛋疼的各种截取和判断字符串,结合 json 结构的字符串来获取 web 端发来的数据。
通过 - stringByEvaluatingJavaScriptFromString:
来拼接老长的字符串来传递数据给 web 端吗。。。
现在我们可以对这种方式说 「爱过~」 了。
先看一下 Objective-C 与 JavaScript 之间类型的对应关系
Objective-C type | JavaScript type |
nil |
undefined |
NSNull |
null |
NSString |
string |
NSNumber |
number, boolean |
NSDictionary |
Object object |
NSArray |
Array object |
NSDate |
Date object |
NSBlock |
Function object |
id |
Wrapper object |
Class |
Constructor object |
返回的 JSValue 转换为 Objective-C 类型的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
- (BOOL)toBool;
- (double)toDouble;
- (int32_t)toInt32;
- (uint32_t)toUInt32;
- (NSNumber *)toNumber;
- (NSString *)toString;
- (NSDate *)toDate;
- (NSArray *)toArray;
- (NSDictionary *)toDictionary;
- (id)toObject;
- (id)toObjectOfClass:(Class)expectedClass;
|
我们主要使用的是 JSContext
这个类,下面通过几个简单的例子来演示。
首先需要加入 JavaScriptCore.framework 并引入。
1
|
#import <JavaScriptCore/JavaScriptCore.h>
|
Objective-C 调用纯 JavaScript
例如有一段计算阶乘的脚本
test.js
1
2
3
4
5
6
7
8
9
|
function factorial(n) {
if (n < 0){
return;
}
if (n === 0){
return 1;
}
return n * factorial(n - 1)
};
|
将脚本文件拖入项目。
注意在项目的 TARGETS > Build Phases 中,把 *.js
文件从 Compile Sources 都拖到 Copy Bundle Resources,否则会有编译警告。
通过 - evaluateScript:
方法把脚本引入 JSContext 对象
1
2
3
4
5
6
|
NSString *path = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"test.js"];
NSString *testScript = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
self.context = [[JSContext alloc] init];
[self.context evaluateScript:testScript];
|
通过 - objectForKeyedSubscript:
传入 function 的名称以取到该方法, 再通过 - callWithArguments:
传入参数并得到返回值(如果有)。
1
2
3
4
5
6
|
NSNumber *inputNumber = [NSNumber numberWithInteger:[self.inputNumberTextField.text integerValue]];
JSValue *function = [self.context objectForKeyedSubscript:@"factorial"];
JSValue *result = [function callWithArguments:@[inputNumber]];
NSNumber *number = [result toNumber];
|
JavaScript 调用 native 代码
首先通过一个协议来关联 JavaScript 的 function 和 native 方法,
还可通过 JSExportAs(functionName, Selector);
来指定 function 的别名。
1
2
3
4
5
6
7
8
9
10
11
12
|
@protocol TestJSExport <JSExport>
JSExportAs
(functionNameForJS /** JavaScript function 的别名 */,
- (void)handleFactorialCalculateWithNumber:(NSNumber *)number
);
- (void)pushToNextViewControllerWithTitle:(NSString *)title;
@end
|
然后在 UIWebView
回调方法 - webViewDidFinishLoad:
中把 web 的 javaScriptContext 与我们用来操作的 JSContext
对象关联,并把 self
设为 JavaScript 调用 function 的对象。
1
2
3
4
5
|
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 以 JSExport 协议关联 native 的方法
self.context[@"native"] = self;
//此处有误,关联到 self 会造成 self 这个对象无法释放的问题,建议使用后面的 block 形式,或者关联到非 self 本身的对象。(weakSelf 也不行)
|
这样在 web 中我们就可以通过所注册的 native 对象来调用到 native 方法。
1
2
3
4
5
6
7
|
<textarea id="input" style="font-size:10pt;color:black;"></textarea>
<input type="button" value="计算阶乘" onclick="native.functionNameForJS(input.value);" />
<a id="push" href="#" onclick="native.pushToNextViewControllerWithTitle('Next VC');">
push to next ViewController
</a>
|
建议使用后面的 block 形式,或者关联到非 self 本身的对象。(weakSelf 也不行)
同样的,先关联 javaScriptContext ,然后直接给 JSContext
对象 - setObject:forKeyedSubscript:
来设定相应 block。
1
2
3
4
5
|
self.context[@"log"] =
^(NSString *str)
{
NSLog(@"%@", str);
};
|
这样在 web 中我们就可以直接调用上面注册的 function
1
|
<input type="button" value="测试log" onclick="log('测试');" />
|
使用 Block 要注意避免循环引用
给 JSContext
的 exceptionHandler
属性赋予相应 Block
1
2
3
4
5
6
|
self.context.exceptionHandler =
^(JSContext *context, JSValue *exceptionValue)
{
context.exception = exceptionValue;
NSLog(@"%@", exceptionValue);
};
|
我们可以来利用一些开源的 JavaScript 库来做一些比 native 代码更方便和易于实现的东西
随便找了一个 Highcharts 图表库来做例子。
注意:Highcharts 开源但不完全免费。个人用户及非商业用途免费,商业用途需要购买许可。
大致的调用代码如下
1
2
3
4
5
6
7
8
9
|
NSArray *the1024Data = @[@33, @41, @32, @51, @42, @103, @136];
NSDictionary *the1024Dict = @{@"name": @"1024", @"data": the1024Data};
NSArray *theCCAVData = @[@8, @11, @21, @13, @20, @52, @43];
NSDictionary *theCCAVDict = @{@"name": @"CCAV", @"data": theCCAVData};
NSArray *seriesArray = @[the1024Dict, theCCAVDict];
[self.context[@"drawChart"] callWithArguments:@[seriesArray]];
|
1
2
3
4
|
function drawChart (seriesArray)
{
...
}
|
详细的代码就不贴了,见项目 JavaScriptCoreSample 。
留言列表