首先请参看一篇文章,作者写的很明白,请参看原地址 http://blog.163.com/m_note/blog/static/208197045201293015844274/

其实,oc和js的交互涉及的就是UIWebview的2个最主要的方法,stringByEvaluatingJavaScriptFromString: 和- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType 。第一个方法的主要功能是注入和执行javascript,具体是注入还是执行,要看你参照中的string的格式,被用来从oc调用js。第二个方法的主要是根据请求的url进行分析,做出响应的处理,被用来从js调用oc。


下面我们分析一下WebViewJavascriptBridge工程,请到github上下载这个工程,注意阅读其中的readme文档!

关于这个工程,这里的bridge是以oc的角度命名方法的,比如这里的send,就是指利用oc代码调用javascript代码,而receive就是指javascript代码调用oc代码。这里的方法命名,log输出都是用的这个规则。知道了这个规则,方便理解代码和log。

ios的demo截图如下,共有4个按钮,上边2个是html中的按钮,下面2个是xib中的按钮,上面的按钮是为了演示js调用oc,下面的是为了演示oc调用js。

第一个按钮的演示的是:js发送data到oc,并调用oc中的默认处理方法处理data,处理完成后,oc回调一个js方法。

第二个按钮演示的是:js发送data到oc,并调用指定名称的oc方法去处理data,处理完成后,oc回调一个js方法。

第三个,和第四个的作用和上边的差不多,只不过是从oc向js发送。

我们看一下“点击左上按钮”这个操作的流程,了解一下实现细节。

a 首先是js方面的处理

调用按钮的onclick方法

button.onclick = function(e) {
e.preventDefault()
var data = 'Hello from JS button'
log('JS sending message', data)
bridge.send(data, function(responseData) {
log('JS got response', responseData)
})
}

send 方法如下

function send(data, responseCallback) {
_doSend({ data:data }, responseCallback)
}

_doSend方法如下

function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime()
responseCallbacks[callbackId] = responseCallback
message['callbackId'] = callbackId
}
sendMessageQueue.push(message)
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
}

这段代码需要分析一下,

responseCallbacks是一个map,负责把函数对象responseCallback和callbackId向关联。

message在传入的时候已经包含了data条目,这里又为它添加了callbackId条目,这样一条信息发送数据和回掉方法都被记录到了message中。

之后系统调用了sendMessageQueue.push(message)向数组加入message。

最后,系统更改iframe元素的src属性,这会导致UIWebView调用代理方法- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType。这样就可以调用相应的oc方法了。


b oc的任务开始了

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (webView != _webView) { return YES; }
NSURL *url = [request URL];
NSLog(@"shouldStartLoadWithRequest url is %@",url);
__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
if ([[url scheme] isEqualToString:kCustomProtocolScheme]) {
if ([[url host] isEqualToString:kQueueHasMessage]) {
[self _flushMessageQueue];
} else {
NSLog(@"WebViewJavascriptBridge: WARNING: Received unknown WebViewJavascriptBridge command %@://%@", kCustomProtocolScheme, [url path]);
}
//注意这里的NO,这样就不会导致WebView加载数据了!因为我们发送的路径根本不是一个有内容的路径
return NO;
} else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
} else {
return YES;
}
}

系统首先检查是不是自己发出的请求,检查方法是查url的scheme,这里是 wvjbscheme  字符串,之后又检查了主机名是不是 __WVJB_QUEUE_MESSAGE__ 字符串。其实这里就属于个人定制部分了,因为这里仅仅是个demo,你可以添加更多的 hostname,去定义更多的处理方法。


之后系统调用_flushMessageQueue方法

这里的WVJBResponseCallback函数代表oc调用js后的回调函数。

系统首先通过执行js,获得到了当前在js中保存的sendMessageQueue,之后对数组中的每条message分别处理。

//主要作用就是取得在js中保存messageQueue,并做出操作
- (void)_flushMessageQueue {
NSString *messageQueueString = [_webView stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"]; id messages = [self _deserializeMessageJSON:messageQueueString];
if (![messages isKindOfClass:[NSArray class]]) {
NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messages class], messages);
return;
}
for (WVJBMessage* message in messages) {
if (![message isKindOfClass:[WVJBMessage class]]) {
NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
continue;
}
[self _log:@"RCVD" json:message]; //检查message字典中是否存在responseId条目,如果存在,那么这条message是js收到oc的调用后,返回的响应消息,这条消息会调用指定的oc回调函数,处理比较简单;如果不存在,那么这是一条js发出的消息,处理比较复杂。
NSString* responseId = message[@"responseId"];
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[_responseCallbacks removeObjectForKey:responseId];
} else {
WVJBResponseCallback responseCallback = NULL;
//callbackId 代表了js的回掉函数,因此,如果存在这个条目,那么oc应该负责发送一条message,去调用js的这个回掉函数,如果不存在那么就不必操作了。
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
responseCallback = ^(id responseData) {
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
} //handlerName 代表js要条用的oc的方法名称,如果存在,那么数据和回掉方法都要由oc中的handler处理;如果不存在,那么系统用创建bridge时传入的handle处理。
WVJBHandler handler;
if (message[@"handlerName"]) {
handler = _messageHandlers[message[@"handlerName"]];
//没有注册相应的handler
if (!handler) {
NSLog(@"WVJB Warning: No handler for %@", message[@"handlerName"]);
return responseCallback(@{});
}
} else {
handler = _messageHandler;
} @try {
id data = message[@"data"];
handler(data, responseCallback);
}
@catch (NSException *exception) {
NSLog(@"WebViewJavascriptBridge: WARNING: objc handler threw. %@ %@", message, exception);
}
}
}
}

系统每处理一条message,就打印出类似于下边的log

WVJB RCVD: {"data":"Hello from JS button","callbackId":"cb_2_1394094418895"}

在js中其实又2个数组保存message,一个是sendMessageQueue代表js向oc发出的消息,另一个是receiveMessageQueue代表oc向js发出的消息。

在oc中只有一个_startupMessageQueue保存message,这些message都是oc向js发送的消息,而js向oc发送的消息,都是通过调用- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType方法进行的,这样就不需要把消息再保存一遍了。

为什么不需要再保存呢?这里就涉及到另一个问题,为什么需要messageQueue呢?我对messageQueue的理解是,不能够或者不方便立即对message做出处理。例如 oc中的这个

_startupMessageQueue,我们看看程序启动时的log

-- ::14.288 ExampleApp-iOS[:a0b] shouldStartLoadWithRequest url is about:blank
-- ::14.289 ExampleApp-iOS[:a0b] webViewDidStartLoad
-- ::14.319 ExampleApp-iOS[:a0b] webViewDidFinishLoad.......
-- ::14.321 ExampleApp-iOS[:a0b] go on
-- ::14.327 ExampleApp-iOS[:a0b] shouldStartLoadWithRequest url is about:blank
-- ::14.329 ExampleApp-iOS[:a0b] webViewDidStartLoad
-- ::14.331 ExampleApp-iOS[:a0b] webViewDidFinishLoad.......
-- ::14.332 ExampleApp-iOS[:a0b] go on
-- ::14.332 ExampleApp-iOS[:a0b] bigger than one!!!!------------
-- ::14.333 ExampleApp-iOS[:a0b] _dispatchMessage : {
callbackId = "objc_cb_1";
data = "A string sent from ObjC before Webview has loaded.";
}
-- ::14.333 ExampleApp-iOS[:a0b] WVJB SEND: {"data":"A string sent from ObjC before Webview has loaded.","callbackId":"objc_cb_1"}
-- ::14.334 ExampleApp-iOS[:a0b] javascript command is WebViewJavascriptBridge._handleMessageFromObjC('{\"data\":\"A string sent from ObjC before Webview has loaded.\",\"callbackId\":\"objc_cb_1\"}');
-- ::14.335 ExampleApp-iOS[:a0b] _dispatchMessage : {
data = {
foo = "before ready";
};
handlerName = testJavascriptHandler;
}
-- ::14.335 ExampleApp-iOS[:a0b] WVJB SEND: {"data":{"foo":"before ready"},"handlerName":"testJavascriptHandler"}
-- ::14.336 ExampleApp-iOS[:a0b] javascript command is WebViewJavascriptBridge._handleMessageFromObjC('{\"data\":{\"foo\":\"before ready\"},\"handlerName\":\"testJavascriptHandler\"}');
-- ::14.336 ExampleApp-iOS[:a0b] _dispatchMessage : {
data = "A string sent from ObjC after Webview has loaded.";
}
-- ::14.337 ExampleApp-iOS[:a0b] WVJB SEND: {"data":"A string sent from ObjC after Webview has loaded."}
-- ::14.337 ExampleApp-iOS[:a0b] javascript command is WebViewJavascriptBridge._handleMessageFromObjC('{\"data\":\"A string sent from ObjC after Webview has loaded.\"}');

结合log,我们再看看启动代码

    [_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"testObjcCallback called: %@", data);
responseCallback(@"Response from testObjcCallback");
}]; [_bridge send:@"A string sent from ObjC before Webview has loaded." responseCallback:^(id responseData) {
NSLog(@"objc got response! %@", responseData);
}]; [_bridge callHandler:@"testJavascriptHandler" data:@{ @"foo":@"before ready" }]; [self renderButtons:webView];
//webview 载入内容
[self loadExamplePage:webView]; [_bridge send:@"A string sent from ObjC after Webview has loaded."];

注意到了吗,在webView载入代码之前,我们就可以发送消息了!但这时候webview没载入内容,当然不能响应我们的message了!所以我们要先把它们缓存起来,等待webViewDidFinishLoad后才能执行所需要的代码。

另外从log中看出,webViewDidFinishLoad是可能执行多次的,(原因是什么呢?) 为了保证messageQueue不被多次执行,需要在执行后把queue设置为nil

- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(@"webViewDidFinishLoad.......");
if (webView != _webView) { return; }
_numRequestsLoading--; if (_numRequestsLoading == && ![[webView stringByEvaluatingJavaScriptFromString:@"typeof WebViewJavascriptBridge == 'object'"] isEqualToString:@"true"]) {
NSBundle *bundle = _resourceBundle ? _resourceBundle : [NSBundle mainBundle];
NSString *filePath = [bundle pathForResource:@"WebViewJavascriptBridge.js" ofType:@"txt"];
NSString *js = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
[webView stringByEvaluatingJavaScriptFromString:js];
} if (_startupMessageQueue) {
if([_startupMessageQueue count]>)
NSLog(@"bigger than one!!!!------------%d",[_startupMessageQueue count]);
for (id queuedMessage in _startupMessageQueue) {
[self _dispatchMessage:queuedMessage];
}
_startupMessageQueue = nil;
} __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
if (strongDelegate && [strongDelegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
[strongDelegate webViewDidFinishLoad:webView];
}
}

iOS 使用UIWebView把oc代码和javascript相关联的更多相关文章

  1. iOS中UIWebView执行JS代码(UIWebView)

    iOS中UIWebView执行JS代码(UIWebView) 有时候iOS开发过程中使用 UIWebView 经常需要加载网页,但是网页中有很多明显的标记让人一眼就能看出来是加载的网页,而我们又不想被 ...

  2. iOS中UIWebView与其中网页的javascript的交互

    首发:个人博客,更新&纠错&回复 1.本地语言调js的方式与android中的方式类似,也是向WebView控件发送要调用的js语句 2. 但js调本地语言,则不是像android那样 ...

  3. iOS下JS与OC互相调用(五)--UIWebView + WebViewJavascriptBridge

    WebViewJavascriptBridge是一个有点年代的JS与OC交互的库,使用该库的著名应用还挺多的,目前这个库有7000+star.我去翻看了它的第一版本已经是4年前了,在版本V4.1.4以 ...

  4. iOS(UIWebView 和WKWebView)OC与JS交互 之二

    在iOS应用的开发过程中,我们经常会使用到WebView,当我们对WebView进行操作的时候,有时会需要进行源生的操作.那么我记下来就与大家分享一下OC与JS交互. 首先先说第一种方法,并没有牵扯O ...

  5. Unity3D研究院之IOS全自动编辑framework、plist、oc代码

    Unity打IOS时会先生成一个Xcode工程,如果你需要增加一些第三方的framework那么需要手动一条一条的添加,这太烦了..而且可能你还需要修改Plist文件,甚至还可能要修改unity自动生 ...

  6. iOS中JS 与OC的交互(JavaScriptCore.framework)

    iOS中实现js与oc的交互,目前网上也有不少流行的开源解决方案: 如:react native 当然一些轻量级的任务使用系统提供的UIWebView 以及JavaScriptCore.framewo ...

  7. iOS - UI - UIWebView

    1.UIWebView UIWebView 是 苹果提供的用来展示网页的UI控件.它也是最占内存的控件. iOS8.0 webkit框架. WKWebView,相比UIWebView,节省了1/3~1 ...

  8. iOS下JS与OC互相调用(四)--JavaScriptCore

    前面讲完拦截URL的方式实现JS与OC互相调用,终于到JavaScriptCore了.它是从iOS7开始加入的,用 Objective-C 把 WebKit 的 JavaScript 引擎封装了一下, ...

  9. ios--网页js调用oc代码+传递参数+避免中文参数乱码的解决方案(实例)

    此解决方案原理: 1.在ViewController.h中声明方法和成员变量,以及webView的委托: // //  ViewController.h //  JS_IOS_01 // //  Cr ...

随机推荐

  1. poj3692 最大点权独立集/最大独立集

    题意:有男孩和女孩,男孩之间全部认识,女孩之间全部认识,一部分男孩和女孩认识,现在希望选出一些孩子,这些孩子都相互认识. 方法:正的做不好做,观察他的补图,补图之间无关系的边就是原图有关系的.补图中的 ...

  2. 获取手机的gps定位

    只要手机有GPS模块,可以用HTML5的Geolocation接口获取 在HTML5中,geolocation作为navigator的一个属性出现,它本身是一个对象,拥有三个方法: - getCurr ...

  3. BZOJ-3668 起床困难综合症 位运算+贪心

    faebdc学长杂题选讲中的题目...还是蛮简单的...位运算写的不熟练... 3668: [Noi2014]起床困难综合症 Time Limit: 10 Sec Memory Limit: 512 ...

  4. 【bzoj1036】 ZJOI2008—树的统计Count

    http://www.lydsy.com/JudgeOnline/problem.php?id=1036 (题目链接) 题意 动态维护树上两点间最大权值和权值和. Solution 裸树链剖分. 这一 ...

  5. 【poj3348】 Cows

    http://poj.org/problem?id=3348 (题目链接) 题意 给出平面上n个点,以这n个点中的一些围成的多边形面积 div 50的最大值. Solution 凸包求面积. 很好做, ...

  6. POJ2411 Mondriaan's Dream

    Description Squares and rectangles fascinated the famous Dutch painter Piet Mondriaan. One night, af ...

  7. on the way to Peking University

    明天就要去北京参加北大夏令营了,希望这次能有所斩获! on the way to Peking University

  8. Xcon2014 && Geekpwn2014

    目录 . 链接器与加载器技术在保护壳上的应用 . android应用市场中的大规模漏洞挖掘 . android模拟躲避检测和应对 . 内核链表的奥秘 . 信号的可发现性 -- wifi之外我们还能做什 ...

  9. eclipse中文乱码问题解决方案

    eclipse之所以会出现乱码问题是因为eclipse编辑器选择的编码规则是可变的.一般默认都是UTF-8或者GBK,当从外部导入的一个工程时,如果该工程的编码方式与eclipse中设置的编码方式不同 ...

  10. javascript的字符串模板

    在其他语言存在字符串内插(string interpolation)或者叫变量内插(Variable interpolation).ES6中的称为template string. 模板字符串使用反引号 ...