上回书说 OC和JS交互的一些准备工作, 下面开始OC和JS交互的重头戏->JS调用OC.
这里我们会用到Safari的开发工具, 不懂的可以自行百度, 或者看这里, 连接上调试器后, 就开始我们的正事儿.
JS调用OC
在Web的html文件中有如下的代码
document.getElementById('btn').onclick = function () {
bridge.callHandler('openCamera', {'count':'10张'}, function responseCallback(responseData) {
console.log("OC中返回的参数:", responseData)
});
};
当点击Web页面的按钮时候, 执行
bridge.callHandler('openCamera', {'count':'10张'}, function responseCallback(responseData) {
console.log("OC中返回的参数:", responseData)
});
这里bridge
是setupWebViewJavascriptBridge
中的函数回调里的参数, 执行callHandler
会来到
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName:handlerName, data:data }, responseCallback);
}
我们之前注入的代码, 在WebViewJavascriptBridge_JS.m文件中, 由于这段代码是在native中, 我们无法调试, 只能一点点分析, 先看参数
1 handlerName
, 这个是JS要调用的native的方法名.
2 data
, 这个是JS要传递给OC的参数.
3 responseCallback
, 这个是JS注册的一个回调方法, 最终由OC调用, 执行代码在JS中, console.log("OC中返回的参数:", responseData)
这里函数体里显示对输入参数进行一些列判断, 暂且不理. 直接看后面的
_doSend({ handlerName:handlerName, data:data }, responseCallback);
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;
}
执行_doSend
, 将请求的方法名和参数进行封装, 封装到message
对象中, 然后根据系统时间获得一个callbackId
, 并把responseCallback
存储在responseCallbacks
字典中, 最后把callbackId
也追加到message
对象中, 现在在message
对象中就存储了JS调用OC前的一些列参数, 最后把这个对象加到sendMessageQueue
数组中, 然后->
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
又是这行代码, 它的目的只有一个, 触发UIWebView执行代理方法shouldStartLoadWithRequest
, 在shouldStartLoadWithRequest
中, 再次来到
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
这段代码, 但是这次和之前注入的时候情况已经不一样了.
首先
WebViewJavascriptBridgeBase.m
-(NSString *)webViewJavascriptFetchQueyCommand {
return @"WebViewJavascriptBridge._fetchQueue();";
}
这里是取出要执行的脚本, 这里直接返回了字符串.
- (NSString*) _evaluateJavascript:(NSString*)javascriptCommand
{
return [_webView stringByEvaluatingJavaScriptFromString:javascriptCommand];
}
然后让_webView
执行这段脚本, 这里又调到了JS, 记住, 只要_webView
执行stringByEvaluatingJavaScriptFromString
就触发了, OC调JS
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
这里JS方法, 首先对sendMessageQueue进行解析, 转成JS的string, 然后返回这个string, 下面的messageQueueString
就是JS返回的string
[_base flushMessageQueue:messageQueueString];
下面重点看
- (void)flushMessageQueue:(NSString *)messageQueueString{
if (messageQueueString == nil || messageQueueString.length == 0) {
NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page.");
return;
}
id messages = [self _deserializeMessageJSON:messageQueueString];
for (WVJBMessage* message in messages) {
if (![message isKindOfClass:[WVJBMessage class]]) {
NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
continue;
}
[self _log:@"RCVD" json:message];
NSString* responseId = message[@"responseId"];
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
} else {
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
if (!handler) {
NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
continue;
}
handler(message[@"data"], responseCallback);
}
}
}
最上面的一段已经分析过了, 是注入的时候传了messageQueueString
为空的串.
- (NSArray*)_deserializeMessageJSON:(NSString *)messageJSON {
return [NSJSONSerialization JSONObjectWithData:[messageJSON dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];
}
这里又把之前JS的串转成OC的数组对象. 然后遍历这个数组对象.
if (![message isKindOfClass:[WVJBMessage class]]) {
NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
continue;
}
这是一段保护代码
实际上
typedef NSDictionary WVJBMessage;
我们知道, WVJBMessage
对象就是一个字典, 这就是大神的代码, 如果是我们菜鸟写代码, 分分钟写成NSDictionary
还不知道有什么不对.
之后NSString* responseId = message[@"responseId"];
取出responseId
字段, 由于之前的分析, 我们知道, 这里并没有存responseId
, 所以responseId==nil
来到else分支
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
if (!handler) {
NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
continue;
}
handler(message[@"data"], responseCallback);
这里取出callbackId
, 然后创建一个WVJBResponseCallback
类型的回调, 然后取出handlerName
, 在根据message[@"handlerName"]
取出真实的WVJBHandler
, 说到这里我们应该去看下到底self.messageHandlers
是什么, 我们找到
WebViewJavascriptBridge.m
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handler copy];
}
在子类registerHandler
的时候会把handler
保存到messageHandlers
字典中, 而registerHandler
是在VC中进行的
[self.bridge registerHandler:@"openCamera" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"需要%@图片", data[@"count"]);
UIImagePickerController *imageVC = [[UIImagePickerController alloc] init];
imageVC.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentViewController:imageVC animated:YES completion:nil];
}];
responseCallback(@{@"data" : @"123"});
执行
handler(message[@"data"], responseCallback);
这行代码, 我们知道, message
中存储的数据转移到了OC, 并且刚才创建的WVJBResponseCallback
回调也传到了OC, 而这里的message[@"data"]
正是之前JS中传递的参数字典.
最后在VC中执行responseCallback(@{@"data" : @"123"});
就调用了
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
这里是之前创建的一个WVJBResponseCallback
, 如果参数不为空, 将OC传递的数据封装成WVJBMessage
, 其实还是NSDictionary
, 开始执行_queueMessage
- (void)_queueMessage:(WVJBMessage*)message {
if (self.startupMessageQueue) {
[self.startupMessageQueue addObject:message];
} else {
[self _dispatchMessage:message];
}
}
if分支走不到, 直接来到else
- (void)_dispatchMessage:(WVJBMessage*)message {
NSString *messageJSON = [self _serializeMessage:message pretty:NO];
[self _log:@"SEND" json:messageJSON];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];
NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
if ([[NSThread currentThread] isMainThread]) {
[self _evaluateJavascript:javascriptCommand];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _evaluateJavascript:javascriptCommand];
});
}
}
这里是把message
字典又转成了字符串, 最后调用JS的_handleMessageFromObjC
方法, 传递message
function _handleMessageFromObjC(messageJSON) {
_dispatchMessageFromObjC(messageJSON);
}
function _dispatchMessageFromObjC(messageJSON) {
if (dispatchMessagesWithTimeoutSafety) {
setTimeout(_doDispatchMessageFromObjC);
} else {
_doDispatchMessageFromObjC();
}
function _doDispatchMessageFromObjC() {
var message = JSON.parse(messageJSON);
var messageHandler;
var responseCallback;
if (message.responseId) {
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
} else {
if (message.callbackId) {
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};
}
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
} else {
handler(message.data, responseCallback);
}
}
}
}
这里
if (dispatchMessagesWithTimeoutSafety) {
setTimeout(_doDispatchMessageFromObjC);
} else {
_doDispatchMessageFromObjC();
}
这段代码暂时先不看, 应该是一些特殊情况的处理, 直接看_doDispatchMessageFromObjC
, 这里将OC传递进来的JSON字符转成了JS的对象message
, 然后取出responseId
, 因为之前在OC里面, 已经执行了
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
这里可以取出callbackId
, 直接取出responseCallback
然后执行responseCallback(message.responseData);
回到JS调用的地方, 我们在Safari中下断点, 看下参数
结果, 确实就是OC传递的参数, 至此, JS调用OC, 并且从OC中带回参数的问题已经圆满解决了.