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
click : 创建一个元素, 赋值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;
// 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
...
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
#import
@protocol NativeApis
-(void) logMessage: (NSString *) message;
-(NSString *) version;
// 异步
-(void) asyncPrint: (NSString *) message;
// callback
-(void) asyncPrint: (NSString *) message callback: (JSValue *) callback;
@end
@interface WBNativeApis : NSObject
@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)时间.
MethodAvgMinMax
location.href1.440.7013.59
location.hash1.000.666.19
iframe.src1.471.055.41
XHR sync1.360.853.44
XHR async0.850.4614.96
document.cookie0.420.211.59
JavaScriptCore0.060.040.13
从表格中可以看出,JavaScriptCore的通信方式性能最好.
兼容性
各种通信方式的兼容性如下(+表示支持,X表示不支持):
Method/DeviceiOS4iOS5iOS6iOS7iOS8
location.href+++++
location.hash+++++
iframe.src+++++
XHR sync+X+++
XHR async+X+++
document.cookie++++X
JavaScriptCoreXXX++
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..postMessage();
function postMyMessage() {
var message = { 'message' : 'Hello, World!', 'numbers' : [ 1, 2, 3 ] };
window.webkit.messageHandlers.myName.postMessage(message);