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 比较麻烦
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./2013/10/a-faster-uiwebview-communication.html
- https://github.com/mihaip/web-experiments/pull/1
- http://www./blog/javascriptcore-example/
- http:///blog_assets/JavaScript%20with%20iOS7.pdf
- http://blog./post/64171814244/true-javascript-uiwebview-integration-in-ios7
|