來源: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 代码

  • JSExport

首先通过一个协议来关联 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 也不行)

 

 

 

  • Block

同样的,先关联 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 。

arrow
arrow
    全站熱搜

    戮克 發表在 痞客邦 留言(0) 人氣()