上回書說 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中帶回參數的問題已經圓滿解決了.