UIWebView 中JavaScript 与 Objective-C 通信

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‘)"];

缺点

以上方法有以下缺点:

  1. 返回值类型只能是字符串类型

    • Objective-C 需要对字符串结果进行反序列化
    • JavaScript 可能需要对结果进行序列化
  2. 调用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的方式, 都有以下缺点:

  1. 整个过程是异步的, 不能同步
  2. JavaScript中不能直接获取Objective-C处理的返回值
    • 需要Objective-C 调用JavaScript层自己实现的api才能得到返回值
  3. 使用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 引入WKWebViewWKWebView 不支持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);?
}

参考资料

  1. http://blog.persistent.info/2013/10/a-faster-uiwebview-communication.html
  2. https://github.com/mihaip/web-experiments/pull/1
  3. http://www.bignerdranch.com/blog/javascriptcore-example/
  4. http://oscaraperez.com/blog_assets/JavaScript%20with%20iOS7.pdf
  5. http://blog.impathic.com/post/64171814244/true-javascript-uiwebview-integration-in-ios7
时间: 2024-07-31 02:49:32

UIWebView 中JavaScript 与 Objective-C 通信的相关文章

UIWebView中javascript与Objective-C交互、获取摄像头

UIWebView是iOS开发中常用的一个视图控件,多数情况下,它被用来显示HTML格式的内容. 支持的文档格式 除了HTML以外,UIWebView还支持iWork, Office等文档格式: Excel (.xls) Keynote (.key.zip) Numbers (.numbers.zip) Pages (.pages.zip) PDF (.pdf) Powerpoint (.ppt) Word (.doc) Rich Text Format (.rtf) Rich Text For

iOS UIWebView与JavaScript的交互 相关资料

UIWebView自适应宽度 iOS UIWebView中javascript与Objective-C交互.获取摄像头 iOS中JavaScript和OC交互 iOS与js交互,获取webview完整url,title,获取元素并赋值跳转 示例 https://github.com/shaojiankui/WebViewJS wanggang316/ClickWebViewImage 点击webview上的图片,调用js获取,展示,保存

在UIWebView中间接调用网页中的javascript代码获得想要的值

日记和一些废话: 今天在使用webView加载网页后, 发现网页中的点击事件是用js代码实现的, 可是怎么点击都没有反应, 而且我主要是想获取到点击事件转到的url , 后来发现html中又这么一段代码: document.addEventListener('WebViewJavascriptBridgeReady', function onBridgeReady(event) function openPage(pageUrl) { if(bridge) { bridge.send(pageU

IOS中UIWebView和JavaScript交互(转自http://blog.2jun.net/2012/11/02/webviewandjs/)

IOS中UIWebView和JavaScript交互 当程序中使用到UIWebView控件的时候,难免会遇到需要与页面进行交互的情况.这种情况在android平台下比较容易处理,android平台下WebView控件的addJavascriptInterface()方法可以很轻松的完成交互,而IOS上就稍复杂一些. 页面与客户端的交互是通过JS来完成的,通常情况下与JS的交互可以分为两种:客户端传递给JS一些数据和JS向客户端请求一些本地操作.下面分别对这两种情况进行处理. JS向客户端请求本地

【iOS开发】UIWebView与JavaScript(JS) 回调交互

引用:http://blog.sina.com.cn/s/blog_693de6100102vi3w.html 很多关于object-c(简称:oc,是ios开发app使用的语言) 与 js 交互的文章都比较适用于 mac开发,iOS的webview 还是有所不一样, 参考:http://blog.sina.com.cn/s/blog_693de6100102vhuh.html 本文提供了一个很好解决 交互的思路. 自然,从oc到js,可以使用 stringByEvaluatingJavaScr

UIWebView与JavaScript的交互

UIWebView是iOS最常用的SDK之一,它有一个stringByEvaluatingJavaScriptFromString方法可以将javascript嵌入页面中,通过这个方法我们可以在iOS中与UIWebView中的网页元素交互. stringByEvaluatingJavaScriptFromString 使用stringByEvaluatingJavaScriptFromString方法,需要等UIWebView中的页面加载完成之后去调用.我们在 界面上拖放一个UIWebView控

UIWebView中JS与OC交互 WebViewJavascriptBridge的使用

一.综述 现在很多的应用都会在多种平台上发布,所以很多程序猿们都开始使用Hybrid App的设计模式.就是在app上嵌入网页,只要写一份网页代码,就可以跑在不同的系统上.在iOS中,app多是通过WebView来加载网页,由于功能需求等原因,代码中少不得要和跟网页交互. 二.原理 在iOS中,本地调用Javascript语言,是通过UIWebView中的实例方法stringByEvaluatingJavaScriptFromString:来实现的,该方法通过字符串对象的形式传入JS代码. [w

JavaScript与Flash的通信

当Flash置于HTML容器中时,经常会遇到AS与JS的通信问题,例如:JS能否调用AS中的变量.方法,AS能否调用JS中的变量.方法等等.答案是肯定的.随着技术的不断发展,解决方案也是多种多样的. 在我总结的HTML与FLASH之间的“静态”传值一文中提到了JS使用SetVariable方法来设置FLASH中的变量,认为此法已经过时.对此我表示同意,但上文重点毕竟不是在讨论JS与AS的通信,因此另外对AS与JS通信做一个个人总结,欢迎大家讨论. 实现JS跟AS的通信,目前可选方法比较多,但早期

UIWebView与JavaScript(JS) 回调交互 -备

很多关于objc 与 js 交互的文章都比较适用于 mac开发,iOS的webview 还是有所不一样, 参考:http://blog.sina.com.cn/s/blog_693de6100102vhuh.html 本文提供了一个很好解决UIWebView内js和objc 交互的思路. 自然,从oc到js,可以使用 stringByEvaluatingJavaScriptFromString: 来实现. 从js到oc,采用比较巧妙的设计,UIWebView浏览器拦截 url请求,自定义url的