The previous article introduced the realization of OC and JS by UIWebView the possibility and implementation of the principle, and a simple implementation of a small sample demo, of course, There are some legacy problems, the use of native implementation process is cumbersome, the code is difficult to Maintain. This article mainly introduces the implementation principles and methods of the open source Library webviewjavascriptbridge, and uses this open source library to rewrite the previous example, as well as the sample code for this article I will give you a welcome star
At the end of the previous article, we briefly introduced the implementation principle of Webviewjavascriptbridge is also based on UIWebView protocol interception, through the reading source discovery, there are many places in the middle is worth learning. Related to the technical ideas of dispatch, data serialization, exception handling, etc.
first, The realization principle of Webviewjavascriptbridge
webviewjavascriptbridge, as its name defines, is the equivalent of a bridge that connects both Obj-c and Javascript. It provides a method interface for OC and JS intermodulation, which we need to register our method list with each other before intermodulation
1. OC Call and Callback JS
1.1 JS Register OC Method and implement callback Ocbridge.registerhandler (' JS echo ', function (data, Responsecallback) {console.log ("js Echo called With: ", Data) Responsecallback (data)})
1.2 OC invokes the JS method and implements the callback function [self.bridge callhandler:@ "js Echo" responsecallback:^ (id responsedata) { NSLog (@ "OBJC Received response:%@ ", responsedata);}];
2. JS Call and callback OC
2.1 OC Register JS Method and implement callback Js[self.bridge registerhandler:@ "OBJC Echo" handler:^ (id data, wvjbresponsecallback Responsecallback) { NSLog (@ "objc Echo called with:%@", data); Responsecallback (data);}];
2.2 JS calls the OC method and implements the callback function Bridge.callhandler (' OBJC Echo ', {' key ': ' value '}, function Responsecallback (responsedata) { Console.log ("JS received response:", Responsedata)})
First look at a model diagram (pictures from the Web)
Through the model diagram, we can see clearly how the bridge that Webviewjavascriptbridge serves
For specific usage, refer to the Official User guide
Let us analyze the Webviewjavascriptbridge implementation principle in Detail:
First look at the library files, a total of 8 files, and we care about only 3 files, because the file 5, 6 is compatible with wkwebview, the implementation principle is the same, do not need to see, all the. h files are declared. m in the method list also does not need to see
1. WebViewJavascriptBridge.h 2. WEBVIEWJAVASCRIPTBRIDGE.M 3. WebViewJavascriptBridgeBase.h 4. WEBVIEWJAVASCRIPTBRIDGEBASE.M 5. WKWebViewJavascriptBridge.h 6. WKWEBVIEWJAVASCRIPTBRIDGE.M 7. Webviewjavascriptbridge_js.h 8. Webviewjavascriptbridge_js.m
Next we need to focus on the code in the file 2,4,8, it is worth mentioning that the file 7,8 is to inject JS related files, Before the JS code is placed in a file called WebViewJavascriptBridge.js.txt, the reason for the change can be found in the head of the file 8 related traces, because it is probably the User's library and its packaging has an impact. next, look at the initialization section of the source code:
1. First we have a bridge object
@property (nonatomic, Strong) Webviewjavascriptbridge *bridge;
2. Then the alignment is initialized, the process of initialization we passed in the current WebView object, bridge in the process of initialization (file 2), as the proxy object of the webview, and set the corresponding platform Agent (ios/osx, which is compatible with two platforms), This way, when the WebView is loaded, Bridge can get the call and processing of the related method. This completes the initialization of the OC end Bridge.
Self.bridge = [webviewjavascriptbridge bridgeForWebView:self.webView];
3. Bridge will trigger the initialization of Bridgebase (file 4), in fact, the processing of messages and callbacks are done in bridgebase, bridge's role is: webview proxy transfer, corresponding platform initialization, Call Bridgebase in the registration, invocation and callback processing of the message, we look at the properties in the Bridgebase header file, There is a message queue array, a message processing dictionary, a response callback dictionary and a specific message processing object
@property (assign) ID <WebViewJavascriptBridgeBaseDelegate> delegate; @property (strong, Nonatomic) nsmutablearray* startupmessagequeue; @property (strong, nonatomic) nsmutabledictionary* responsecallbacks; @property ( strong, nonatomic) nsmutabledictionary* messagehandlers; @property (strong, nonatomic) wvjbhandler messageHandler;
4. When the WebView first load, we look at the following code, first determine whether it is the current scheme, and then determine whether the URL is the first loaded url, the message queue-related URL definition and the first loaded URL definition and processing method is different, so here to distinguish, The first time we load, we will inject the relevant JS code (file 8), followed by the specific (file 8) Code. Message queuing-related URLs We need to respond to messages
-(BOOL) webView: (uiwebview *) webView shouldstartloadwithrequest: (nsurlrequest *) Request Navigationtype: ( Uiwebviewnavigationtype) navigationtype {if (webView! = _webview) {return YES;} Nsurl *url = [request url]; __strong wvjb_webview_delegate_type* strongdelegate = _webviewdelegate; If ([_base iscorrectprocotocolscheme:url]) {if ([_base isbridgeloadedurl:url]) {[_base Injectjavascrip tfile]; } else if ([_base isqueuemessageurl:url]) {nsstring *messagequeuestring = [self _evaluatejavascript:[_base web viewjavascriptfetchqueycommand]]; [_base flushmessagequeue:messagequeuestring]; } else {[_base logunkownmessage:url]; } return NO; } else if (strongdelegate && [strongdelegate respondstoselector: @selector (webview:shouldstartloadwithrequest : navigationtype:)] {return [strongdelegate webview:webview shouldstartloadwithrequest:request NavigationType:navi gationtype]; } else { Return YES; }}
5. The first time we load the JS code, execute the "_evaluatejavascript:" method, the actual call is WebView in the "stringbyevaluatingjavascriptfromstring:" method to inject the JS code, after injection if the queue message has a message, the message will be called
-(void) injectjavascriptfile { nsstring *js = Webviewjavascriptbridge_js (); [self _evaluatejavascript:js]; If (self.startupmessagequeue) { nsarray* queue = self.startupmessagequeue; Self.startupmessagequeue = nil; For (id queuedmessage in Queue) { [self _dispatchmessage:queuedmessage];}}
6. Next we look at the JS side of the part of the code, injected this part of the JS code first for the HTML window added a bridge object, the object has a specific message queue array, message processing dictionary, response callback dictionary and specific message processing object, And the OC end of the bridge is consistent, to complete the initialization of the JS section here, the entire initialization is completed
If (WINDOW. Webviewjavascriptbridge) { return;} Window. Webviewjavascriptbridge = { registerhandler:registerhandler, callhandler:callhandler, _fetchQueue: _ fetchqueue, _handlemessagefromobjc: _handlemessagefromobjc};var messagingiframe;var sendMessageQueue = [];var Messagehandlers = {};
We follow the OC call JS method and callback This line, continue to view the source Code:
7. The above mentioned the OC call JS method, The first need to register the method on the JS side, we look at the Method's registration code, The function definition part is constant, as long as the use of this library, then this part of the code is necessary, the call function passed in the callback part of the code is our own need to customize the place, The function name in Registerhandler here is the method name that OC will call Js.
function Setupwebviewjavascriptbridge (callback) { if (window. Webviewjavascriptbridge) {return Callback (webviewjavascriptbridge);} If (WINDOW. Wvjbcallbacks) {return WINDOW. Wvjbcallbacks.push (callback); } Window. Wvjbcallbacks = [callback]; var wvjbiframe = document.createelement (' iframe '); WVJBIframe.style.display = ' None '; WVJBIFRAME.SRC = ' wvjbscheme://__bridge_loaded__ '; Document.documentElement.appendChild (wvjbiframe); SetTimeout (function () {document.documentElement.removeChild (wvjbiframe)}, 0)}setupwebviewjavascriptbridge ( function (bridge) { //1 Register JS method to OC bridge.registerhandler (' js Echo ', function (data, Responsecallback) {
Console.log ("JS Echo called with:", data) responsecallback (data)} )
8. OC Call when Callhandler finally came to the bridgebase in the following method, the first to encapsulate this call, and internally cache the callback method, and then the dispatch of the message
-(void) senddata: (id) Data responsecallback: (wvjbresponsecallback) responsecallback handlerName: (nsstring*) HandlerName { nsmutabledictionary* message = [nsmutabledictionary dictionary]; If (data) { message[@ "data"] = data; } If (responsecallback) { nsstring* callbackid = [nsstring stringwithformat:@ "objc_cb_%ld", ++_uniqueid]; self.responsecallbacks[callbackid] = [responsecallback copy]; message[@ "callbackid"] = callbackid; } If (handlerName) { message[@ "handlerName"] = handlerName; } [self _queuemessage:message];}
9. After getting the corresponding message, the first thing to serialize into a string object, because WebView executes the JavaScript script to accept a string object, followed by some column conversion processing, in the following code we notice that there is such a string " WEBVIEWJAVASCRIPTBRIDGE._HANDLEMESSAGEFROMOBJC ", This is the JS script we injected in the process of the OC method of the function, here we successfully passed the OC message to JS
-(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]; }); }}
. js to get this message, the dispatch of the message, the first to deserialize the string to get, the method name and parameters, and the method identifier, need to mention, here first to check the JS side cache callback method, through the identifier, we can find the corresponding callback function, if there is a call method, There is no direct call to the method, the following code is the process, the OC call JS to complete the
function _DISPATCHMESSAGEFROMOBJC (messagejson) {setTimeout (function _timeoutdispatchmessagefromobjc () {var mes Sage = 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 ({responseid:callbackresponseid, RESPONSEDATA:RESPO nsedata}); }; } var handler = messagehandlers[message.handlername]; Try {handler (message.data, responsecallback); } catch (exception) {console.log ("WebViewJavascriptBridge:WARNING:javascript handler Threw. ", message, exception); } If (!handler) {console.log ("WebViewJavascriptBridge:WARNING:no handler for message from OBJ C: ", message); } } });}
Next we call the OC method along with JS and callback this line, view the source code:
11. Also we need to register the method after the Oc-side initialization (mentioned earlier), after registration we call JS Callhandler,callhandler when the callback function of the cache, and then into the message queue to put the message, and then redirect the Code Call of OC
function Callhandler (handlerName, data, responsecallback) { if (arguments.length = = 2 && typeof data = = ' Funct Ion ') { responsecallback = data; data = null; } _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;}
12. As mentioned earlier, redirects are injected into the JS code for the first Time. Here is the processing of message queuing, first checking the Message Queuing string, and then deserializing the string as a JSON object, where the result of deserialization is a dictionary containing the array (key-value pairs), followed by the Message's identifier, if the corresponding message has a callback function information, The call to the callback function is then
-(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 are 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); } }}
13. Here we also notice that the JS event at the time of redirection is not after the URL concatenation of related strings, then the message queue string in the previous step is how to come, look at the source code, we also call a method before performing this step, we executed a JS script, This JS script finally called the Fetchqueue,fetchqueue splicing the cache on the JS side of the message queue, returned the message queue string, here and OC JS is a difference
NSString *messagequeuestring = [self _evaluatejavascript:[_base webviewjavascriptfetchqueycommand]; [_base flushmessagequeue:messagequeuestring];//----------------------------------//-(nsstring *) Webviewjavascriptfetchqueycommand { return @ "webviewjavascriptbridge._fetchqueue ();";}
function _fetchqueue () { var messagequeuestring = json.stringify (sendmessagequeue); Sendmessagequeue = []; Return messagequeuestring;}
14. Here the basic principle of this library has been explained very clearly, by reading the source code, you can learn a lot of efficient coding methods, and programming ideas, here summarizes the whole process:
A. The OC and JS end have a bridge object that handles the Manager's two-side interaction method
B. Each bridge has a method of registering, invoking, and caching the callback function
C. Method names and parameters are serialized and deserialized during delivery
D. The callback function is detected by an identifier at the time the method is called and the entire process is completed by invoking the
second, use Webviewjavascriptbridge rewrite
Understanding the principle of the entire open source library to write code can be described as Downwind. here, The previous example is rewritten with webviewjavascriptbridge, and the implementation process is simple and the main code is posted below
OC end:
1 The method of registering OC gives Js[self.bridge registerhandler:@ "showmobile" handler:^ (id data, wvjbresponsecallback responsecallback) {[ Self showmsg:@ "i am the following Little Red mobile phone number is: 18870707070"];}]; [self.bridge registerhandler:@ "showname" handler:^ (id data, wvjbresponsecallback Responsecallback) {nsstring *info = [nsstring stringwithformat:@ "hello%@, Nice to meet you", data]; [self showmsg:info];}]; [self.bridge registerhandler:@ "showsendmsg" handler:^ (id data, wvjbresponsecallback Responsecallback) {nsdictionary *dict = (nsdictionary *) data; NSString *info = [nsstring stringwithformat:@ "this is my mobile number:%@,%@!", dict[@ "mobile"],dict[@ "events"]; [self showmsg:info];}];/ /2 Call JS Register to OC Method-(ibaction) btnclick: (UIButton *) sender {if (sender.tag = = 123) {[self.bridge callhandler:@] Ale Rtmobile "]; } if (sender.tag = = 234) {[self.bridge callhandler:@ "alertname" data:@ "little red"]; } if (sender.tag = = 345) {[self.bridge callhandler:@ "alertsendmsg" data:@{@ "mobile": @ "18870707070@ "events": @ "the Weekend climb is really a pleasant thing"}]; }}
JS End:
function Setupwebviewjavascriptbridge (callback) {if (window. Webviewjavascriptbridge) {return Callback (webviewjavascriptbridge);} If (WINDOW. Wvjbcallbacks) {return WINDOW. Wvjbcallbacks.push (callback); } window. Wvjbcallbacks = [callback]; var wvjbiframe = document.createelement (' iframe '); WVJBIframe.style.display = ' None '; WVJBIFRAME.SRC = ' wvjbscheme://__bridge_loaded__ '; Document.documentElement.appendChild (wvjbiframe); SetTimeout (function () {document.documentElement.removeChild (wvjbiframe)}, 0)}setupwebviewjavascriptbridge ( function (bridge) {//1 Register JS method to OC Bridge.registerhandler (' alertmobile ', function (data, Responsecallback) { Alert (' I'm The little yellow mobile number above: 13300001111 ')}) bridge.registerhandler (' alertname ', function (data, Responsecallback) { Alert (' Hello ' + data + ', I'm Glad to see you too ')}) bridge.registerhandler (' alertsendmsg ', function (data, responsec Allback) {alert (' This is my phone number: ' + data[' mobile '] + ', ' + data[' events '] + '! ') })})//2 calls OC registered to the method jsfunction BtnClick1 () {window. Webviewjavascriptbridge.callhandler (' showmobile ')}function btnClick2 () {window. Webviewjavascriptbridge.callhandler (' showmobile ', ' Little yellow ')}function btnClick3 () {window. Webviewjavascriptbridge.callhandler (' showsendmsg ', {' mobile ': ' 13300001111 ', ' events ': ' go hiking on weekends ')}
Here is a simple one-way interaction, because the previous design is relatively simple, rewrite only completed the corresponding function, there is no callback Module. Examples of callbacks are given by the official sample code, which can be referenced in the official sample code
third, PostScript
After iOS7, Apple has an official library JavaScriptCore for javascript, a JavaScript engine that objective-c encapsulates webkit, allowing us to execute the JS code out of WEBVIEW. So after IOS7 want to realize the interaction, the adoption of JavaScriptCore is also a good choice, if your project does not need to be compatible with iOS7 before, we will introduce the composition and use of JavaScriptCore in the next article
Poke Here: Demo Address of this article welcome star
The Webviewjavascriptbridge of the interaction between OC and JS