你中有我,我中有你。

今天分享下最近学习到的:在 Mac OS X 平台上,实现 Javascript 与 Objective-C 两种编程语言的交互机制。

你侬我侬,忒煞情多。
情多处热如火。
把一块泥,捻一个你,
塑一个我,将咱两个,
一齐打破,用水调和,
再捻一个你,再塑一个我。
我泥中有你,你泥中有我,
与你生同一个衾(qin),
死同一个椁(guo)。

背景

现在很多视频或者音乐的客户端,比如 QQ Music For Mac。往往通过在客户端嵌入网页的方式来展现内容,并通过 Javascript 调用客户端的一些功能,比如下载视频或者播放音乐等。

嵌入网页基本都是采用 WebView 控件来展示网页,这时客户端好像内嵌了一个微型的浏览器。

用嵌入网页的方式,好处是显然的:

  • 网页可以轻易做出很炫丽的效果,当然客户端通过艰难的自绘也可以做出很华丽的效果,但开发时间跟开发难度跟网页比起来没得比。

  • 网页可以随时更改,更改后可以实时更新到你发出去的每一个客户端版本,这种扩展性在实战中是很重要的。

Javascript 与 Objective-C 双方是对等的,也就是 JS 可以调用 OC,反过来 OC 也可以调用 JS。

Demo

  • 官方例子: CallJS ,详细了介绍上面所述的所有技术细节。

  • 你也可以在 GitHub 上直接下载我写的这个 Demo:JS-OC-Communicate-Tutorial

技术细节

类型转换

从 Demo 中可以看出,在 OC 与 JS 通信时,变量的类型会自动进行转换,基本类型都会自动转换,如 JS 中的 number、boolean 都会转换成 OC 中的 NSNumber 类型,而 String 类型会自动转换成 NSString 类型,JS 中的对象会转换成 WebScriptObject 对象,而相关的属性信息可以通过 Key-Value 的方法读取和写入。

OC 调用 JS:

1、OC 执行一段 JS 代码,使用下面的函数:

- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

2、OC 调用 html 页面里面的 JS 函数

比如 js 端有一个函数 JSFunction:

function JSFunction(parameter)
{
    //显示OC返回的值
    document.getElementById('view').value = parameter;

    //返回成功的消息
    var result = {'message':'Web page has received msg!'};
    return result;
}

OC 端可以用下面的方法来调用:

- (IBAction)doAction:(id)sender
{
    //设置对象
    WebScriptObject *result = [[self.webView windowScriptObject] callWebScriptMethod:@"JSFunction" withArguments:@[self.textField.stringValue]];

    NSString *message = [result valueForKey:@"message"];
}

JS 调用 OC:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/* 当页面加载完成,将要执行JS之前,didClearWindowObject 消息将会发送给 webview 的frameLoadDelegate。我们可以在这个最完美的时机里,设置一个属于我们自己的JS对象
*/
- (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame
{
/*将当前对象加到JS里面的window全局对象里面,并命名为native。
之后可以在JS通过使用native变量来使用当前的对象
*/
[windowObject setValue:self forKey:@"native"];
}

/*
返回是否阻止响应该方法,
返回NO即能响应该方法
*/
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)selector
{
if (selector == @selector(status:))
{
return NO;
}
return YES;
}

/*
返回本地方法在JS中的名称
*/
+ (NSString *)webScriptNameForSelector:(SEL)sel
{
if (sel == @selector(status:))
{
return @"ocMethod";
}
return nil;
}

/*
返回是否阻止获取该属性,
返回NO即能获取该属性
*/
+ (BOOL)isKeyExcludedFromWebScript:(const char *)property
{
if (strcmp(property, "sharedValue") == 0)
{
return NO;
}
return YES;
}

/*
返回本地属性在JS中的名称
*/
+ (NSString *)webScriptNameForKey:(const char *)name
{
if (strcmp(name, "sharedValue"))
{
return @"oc_sharedValue";
}
return nil;
}

JS 中代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
function CallNative()
{
if (native)
{
//将当前显示的文本组装成对象发给OC
var consoleValue = document.getElementById('view').value;
var parameter = {'message':consoleValue};
var result = native.ocMethod(parameter);

//显示OC返回的结果
document.getElementById('view').value = result['message'];
}
}

总结

利用 Javascript 与 Objective-C 的通信机制,可以通过 HTML、Javascript、CSS 等 Web 技术实现炫丽的 UI ,而通过原生 API 实现与本地相关的操作。这对于 OSX 平台上的桌面客户端开发者而言是大大的福音。

参考

彦祖老师 wechat