iOS7 之前
Objective-C -> JavaScript
UIWebView
对象有以下方法
- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script
该方法能够执行一段JavaScript
字符串, 并返回字符串类型的返回值. 例如:
UIWebView *webView = [[UIWebView alloc] init];
// result == @"3"
NSString *result = [webView stringByEvaluatingJavaScriptFromString:@"1+2"];
// 调用js 对象的方法
NSString *result2 = [webView stringByEvaluatingJavaScriptFromString:
@"window.objectApis.doSomething('hello')"];
缺点
以上方法有以下缺点:
- 返回值类型只能是字符串类型
Objective-C
需要对字符串结果进行反序列化JavaScript
可能需要对结果进行序列化
- 调用
JavaScript
对象的方法时, 传入参数比较麻烦Objective-C
需要对参数进行序列化JavaScript
可能需要对字符串参数进行反序列化
// 调用js 对象的方法
NSString *result2 = [webView stringByEvaluatingJavaScriptFromString:
@"window.objectApis.doSomething('hello \" world')"];
JavaScript -> Objective-C
URL
请求截获
在UIWebView
的浏览器的JavaScript
中, 没有相关的接口可以调用Objective-C
的相 关方法. 一般采用JavaScript
在浏览器环境中发出URL
请求, Objective-C
截获请 求以获取相关请求的思路. 在Objective-C
中在实现UIWebViewDelegate
时截获请求:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
NSString *url = request.URL;
// if (url是自定义的JavaScript通信协议) {
//
// do something
//
// 返回 NO 以阻止 `URL` 的加载或者跳转
// return NO;
// }
}
Objective-C
可以在webView:shouldStartLoadWithRequest:navigationType
方法中可以返回NO
以阻止URL
的加载或者跳转.
JavaScript
有各种不同的方式发出URL
请求:
- location.href : 修改
window.location.href
替换成一个合成的URL
, 比如async://method:args
- location.hash : 修改
window.location.hash
<a>
click : 创建一个<a>
元素, 赋值href
属性, 并调用其click()
方法- iframe.src : 创建一个
iframe
元素, 赋值src
属性 - XHR sync/async : 创建一个
XMLHttpRequest
对象,open()
中设置相关信息及是否异步, 并调用send()
方法发出请求
var linkNode = document.createElement("a");
var pongUrl;
var xhr = new XMLHttpRequest();
var iframe = document.createElement("iframe");
iframe.style.display = "none";
function ping(mechanism, startTime) {
pongUrl = "pong://" + startTime;
switch (mechanism) {
// location.href
case Mechanism.LocationHref:
location.href = pongUrl;
break;
// location.hash
case Mechanism.LocationHash:
location.hash = "#" + pongUrl;
break;
// <a> click
case Mechanism.LinkClick:
linkNode.href = pongUrl;
linkNode.click();
break;
// iframe. src
case Mechanism.FrameSrc:
iframe.src = pongUrl;
document.body.appendChild(iframe);
document.body.removeChild(iframe);
break;
// XHR sync/async
case Mechanism.XhrSync:
case Mechanism.XhrAsync:
xhr.open("GET", pongUrl, mechanism == Mechanism.XhrAsync);
xhr.send();
break;
}
}
监听Cookie
在UIWebView
中, Objective-C
可以通过NSHTTPCookieManagerCookiesChangedNotification
事件以监听cookie的变化.
NSNotificationCenter *center = NSNotificationCenter.defaultCenter;
[defaultCenter addObserverForName:NSHTTPCookieManagerCookiesChangedNotification
object:nil
queue:nil
usingBlock:^(NSNotification *notification) {
NSHTTPCookieStorage *cookieStorage = notification.object;
// do something with cookieStorage
}];
当JavaScript
修改 document.cookie
后, Objective-C
可以通过分析cookie以得到信息.
缺点
无论是URL
请求截获方式还是监听Cookie的方式, 都有以下缺点:
- 整个过程是异步的, 不能同步
- 在
JavaScript
中不能直接获取Objective-C处理的返回值- 需要
Objective-C
调用JavaScript
层自己实现的api才能得到返回值
- 需要
- 使用
callback
比较麻烦- 需要在
JavaScript
上自己实现
- 需要在
iOS 7+
iOS7 引入了JavaScriptCore
, 是的JavaScript
和 Objective-C
可以互操作.
Objective-C
可以使用JSContext
的 evalueScript()
方法调用JavaScript
提供 的方法.
#import <JavaScriptCore/JavaScriptCore.h>
...
UIWebView *webView = [[UIWebView alloc] init];
JSContext *jsContext = [webView valueForPath: @"documentView.webView.mainFrame.javaScriptContext"];
// call javascript
[jsContext evalueScript: @"window.objectApis.doSomething()"];
将实现JSExport
协议的对象直接赋值给JSContext
对象的属性即可暴露方法给JavaScript
.
// provide obj-c apis
WBNativeApis *nativeApis = [[WBNativeApis alloc] init];
jsContext[@"nativeApis"] = nativeApis;
// `WBNativeApis` Class
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol NativeApis <JSExport>
-(void) logMessage: (NSString *) message;
-(NSString *) version;
// 异步
-(void) asyncPrint: (NSString *) message;
// callback
-(void) asyncPrint: (NSString *) message callback: (JSValue *) callback;
@end
@interface WBNativeApis : NSObject <NativeApis>
@end
在浏览器环境中使用JavaScript
调用Objective-C
的api
window.nativeApis.logMessage('A message from javascript!');
window.asyncPrintCallback('Message from javascript!', function (data) {
var div = document.createElement('div');
div.innerText = "Send message to native ok and get data from native";
document.body.appendChild(div);
});
JavaScriptCore
将各种类型数据在不同编程语言间做了转换, 可进行直接操作.
Objective-C type | JavaScript type
--------------------+---------------------
nil | undefined
NSNull | null
NSString | string
NSNumber | number, boolean
NSDictionary | Object object
NSArray | Array object
NSDate | Date object
NSBlock (1) | Function object (1)
id (2) | Wrapper object (2)
Class (3) | Constructor object (3)
性能测试
在iPhone 5S (ios 7.1) 模拟器条件下测试各种通信方式一次通信花费的毫秒(ms)时间.
Method | Avg | Min | Max |
---|---|---|---|
location.href | 1.44 | 0.70 | 13.59 |
location.hash | 1.00 | 0.66 | 6.19 |
<a> click | 1.40 | 0.66 | 15.29 |
iframe.src | 1.47 | 1.05 | 5.41 |
XHR sync | 1.36 | 0.85 | 3.44 |
XHR async | 0.85 | 0.46 | 14.96 |
document.cookie | 0.42 | 0.21 | 1.59 |
JavaScriptCore | 0.06 | 0.04 | 0.13 |
从表格中可以看出, JavaScriptCore
的通信方式性能最好.
兼容性
各种通信方式的兼容性如下( +
表示支持, X
表示不支持):
Method/Device | iOS4 | iOS5 | iOS6 | iOS7 | iOS8 |
---|---|---|---|---|---|
location.href | + | + | + | + | + |
location.hash | + | + | + | + | + |
<a> click | + | + | + | + | + |
iframe.src | + | + | + | + | + |
XHR sync | + | X | + | + | + |
XHR async | + | X | + | + | + |
document.cookie | + | + | + | + | X |
JavaScriptCore | X | X | X | + | + |
WKWebView (iOS 8 + )
iOS 8 引入WKWebView
, WKWebView
不支持JavaScriptCore
的方式但提供message handler的方式为JavaScript
与Objective-C
通信.
在Objective-C
中使用WKWebView
的以下方法调用JavaScript
:
- (void)evaluateJavaScript:(NSString *)javaScriptString
completionHandler:(void (^)(id, NSError *))completionHandler
如果JavaScript
代码出错, 可以在completionHandler
进行处理.
在Objective-C
中注册 message handler:
// WKScriptMessageHandler protocol
- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)message
{
NSLog(@"Message: %@", message.body);
}
[userContentController addScriptMessageHandler:handler name:@"myName"];
在JavaScript
将信息发给Objective-C
:
// window.webkit.messageHandlers.<name>.postMessage();
function postMyMessage()
{
var message = { 'message' : 'Hello, World!', 'numbers' : [ 1, 2, 3 ] };
window.webkit.messageHandlers.myName.postMessage(message);
}
参考资料
- http://blog.persistent.info/2013/10/a-faster-uiwebview-communication.html
- https://github.com/mihaip/web-experiments/pull/1
- http://www.bignerdranch.com/blog/javascriptcore-example/
- http://oscaraperez.com/blog_assets/JavaScript%20with%20iOS7.pdf
- http://blog.impathic.com/post/64171814244/true-javascript-uiwebview-integration-in-ios7