Excellent open-source code interpretation: an elegant implementation solution for intermodulation of JS and iOS native code

Source: Internet
Author: User
Introduction

This article introduces a good small open-source project: webviewjavascriptbridge.

It elegantly implements intercommunication between JS and iOS objc nativecode when uiwebview is used. It supports message sending, receiving, message processor registration and calling, and setting Message Processing callback.

Just like the project name, it is the bridge connecting uiwebview and JavaScript. After joining this project, the interaction between them becomes friendly.

When interacting with Js in uiwebview in native code, it is as follows:

// Send a message to the UI end and define the callback processing logic [_ bridge send: @ "a string sent from objc before webview has loaded. "responsecallback: ^ (ID error, Id responsedata) {If (error) {nslog (@" Uh oh-I got an error: % @ ", error );} nslog (@ "objc got response! % @ ", Error, responsedata) ;}];

The interaction between JS and native code in uiwebview also becomes concise. For example, you can define the callback processing logic when calling the processor:

// Call the native processor named testobjccallback, PASS Parameters, and set the callback processing logic bridge. callhandler ('testobjccallback', {'foo': 'bar'}, function (response) {log ('got response from testobjccallback', response )})

Let's take a look at its implementation. It contains three files in total:

WebViewJavascriptBridge.hWebViewJavascriptBridge.mWebViewJavascriptBridge.js.txt

They interact in the following mode:

Obviously: webviewjavascriptbridge.js.txt is mainly used to link web pages in uiwebview, while webviewjavascriptbridge. h/m is mainly used to deal with the native code of objc. As a whole, they actually act as a "bridge". These three files encapsulate their specific interactive processing methods and only open some APIs that involve external business processing, therefore, when you need to introduce the library for uiwebview and native code interaction, you do not need to consider too many interaction issues. The entire bridge is transparent to you. You feel that when programming, it is as clear as the front-end and back-end of WEB programming.

Briefly list the functions that can be implemented:

For the sake of expression, I call it the UI side for uiwebview, while the processing code of the objc side is called the native side.

[1] UI

(1) The UI end supports setting the default message processor during initialization (the message here refers to the message received from the native end)

(2) send messages from the UI side to the native side and support the definition of callback processing after the native side responds

(3) The UI calls the native defined processor and supports the callback processing definition after the native responds.

(4) The UI registers the processor (called by native) and supports defining the response processing logic for native.

[2] native end

(1) Native supports setting the default message processor during initialization (the message here refers to the message sent from the UI)

(2) send messages from native to UI and support definition of callback processing logic after UI response

(3) Native calls the UI-defined processor and supports the definition of callback processing logic on native after the UI sends a response

(4) Native registers the processor (called by the UI) and supports defining the response processing logic for the UI.

The UI end and the native end are both equal ends, and the implementation is also equal. One is the sending end of the message, and the other is the receiving end. For obfuscation, we need to explain the definitions of "response" and "Callback" in this context:

(1) response: the receiving end replies to the sending end.

(2) callback: processing logic called at the receiving end after the sender receives the response from the receiving end

The source code is analyzed below:

Webviewjavascriptbridge.js.txt:

Mainly completed the following work:

(1) Create an IFRAME for sending messages (send a request by creating a hidden ifrmae and setting its URL to trigger the shouldstartloadwithrequest callback protocol of uiwebview)

(2) create a core object webviewjavascriptbridge and define several methods for it. Most of these methods are open API methods.

(3) create an event: webviewjavascriptbridgeready, and dispatch (trigger) it.

Code interpretation UI implementation

For (1), the corresponding code is as follows:

/** Create an IFRAME, set it to hide and add it to the Dom */function _ createqueuereadyiframe (DOC) {messagingiframe = Doc. createelement ('iframe') messagingiframe. style. display = 'none'doc.doc umentelement. appendchild (messagingiframe )}

For webviewjavascriptbridge in (2), the object has the following methods:

window.WebViewJavascriptBridge = {init: init,send: send,registerHandler: registerHandler,callHandler: callHandler,_fetchQueue: _fetchQueue,_handleMessageFromObjC: _handleMessageFromObjC}

Method implementation:

/** Initialization method, injecting the default message processor * the default message processor is used to process messages from objc. if the message is not set as a processor, the default processor is used to process */function Init (messagehandler) {If (webviewjavascriptbridge. _ messagehandler) {Throw new error ('webviewjavascriptbridge. init called twice ')} webviewjavascriptbridge. _ messagehandler = messagehandlervar receivedmessages = receivemessagequeuereceivemessagequeue = NULL // if the receiving queue has a message, it is processed for (VAR I = 0; I <receivedmessages. length; I ++) {_ dispatchmessagefromobjc (receivedmessages [I])}/** send a message and set the callback */function send (data, responsecallback) {_ dosend ({data: Data}, responsecallback)}/** register the message processor */function registerhandler (handlername, Handler) {messagehandlers [handlername] = handler}/** call the processor and set the callback */function callhandler (handlername, Data, responsecallback) {_ dosend ({data: data, handlername: handlername}, responsecallback )}

Two internal methods involved:

/** Internal method: Message sending */function _ dosend (message, responsecallback) {// If callback if (responsecallback) is defined) {// generate a unique identifier for the callback object var callbackid = 'js _ CB _ '+ (uniqueid ++) // and stored in a collection object responsecallbacks [callbackid] = responsecallback // Add a key-Value Pair-'callbackid': callbackidmessage ['callbackid'] = callbackid} sendmessagequeue. push (JSON. stringify (Message) messagingiframe. src = custom_protocol_scheme + ': //' + queue_has_message}/** internal method: process messages from objc */function _ dispatchmessagefromobjc (messagejson) {setTimeout (function _ timeout () {var message = JSON. parse (messagejson) var messagehandlerif (message. responseid) {// retrieve the callback function object and execute var responsecallback = responsecallbacks [message. responseid] responsecallback (message. error, message. responsedata) delete responsecallbacks [message. responseid]} else {var responseif (message. callbackid) {var callbackresponseid = message. callbackidresponse = {respondwith: function (responsedata) {_ dosend ({responseid: callbackresponseid, responsedata: responsedata})}, respondwitherror: function (error) {_ dosend ({responseid: callbackresponseid, error: Error}) }}var handler = webviewjavascriptbridge. _ messagehandler // if the message contains a message processor, use this processor; otherwise, use the default processor if (message. handlername) {handler = messagehandlers [message. handlername]} Try {handler (message. data, response)} catch (exception) {console. log ("webviewjavascriptbridge: Warning: javascript handler threw. ", message, exception )}}})}

Two other JS methods are directly called by native (they are also serving native ):

/** Obtain the queue. Each element in the queue is separated by a separator and connected to a string [native call] */function _ fetchqueue () {var messagequeuestring = sendmessagequeue. join (message_separator) sendmessagequeue = [] Return messagequeuestring}/** process messages from objc [native call] */function _ handlemessagefromobjc (messagejson) {// if the receiving queue object exists, the message is sent to the queue. Otherwise, if (receivemessagequeue) {receivemessagequeue is processed directly. push (messagejson)} else {_ dispatchmessagefromobjc (messagejson )}}

The last piece of code is to define and trigger an event, and set the webviewjavascriptbridge object defined above as an attribute of the event:

VaR Doc = document_createqueuereadyiframe (DOC) // create and instantiate an event object var readyevent = Doc. createevent ('events') readyevent. initevent ('webviewjavascriptbridgeready') readyevent. bridge = webviewjavascriptbridge // trigger event Doc. dispatchevent (readyevent)

Native implementation

In fact, it is roughly the same as above, just because the syntax is different (so I mentioned above that the two ends are equivalent ):

Webviewjavascriptbridge. h/. m

It can be viewed as the controller of uiwebview and implements the uiwebviewdelegate protocol:

@interface WebViewJavascriptBridge : NSObject <UIWebViewDelegate>+ (id)bridgeForWebView:(UIWebView*)webView handler:(WVJBHandler)handler;+ (id)bridgeForWebView:(UIWebView*)webView webViewDelegate:(id <UIWebViewDelegate>)webViewDelegate handler:(WVJBHandler)handler;+ (void)enableLogging;- (void)send:(id)message;- (void)send:(id)message responseCallback:(WVJBResponseCallback)responseCallback;- (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler;- (void)callHandler:(NSString*)handlerName;- (void)callHandler:(NSString*)handlerName data:(id)data;- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;@end

The method is actually similar to the previous one. Here we only look at a protocol method of uiwebview.

Shouldstartloadwithrequest:

-(Bool) webview :( uiwebview *) webview shouldstartloadwithrequest :( nsurlrequest *) Request navigationtype :( uiwebviewnavigationtype) navigationtype {If (webview! = _ Webview) {return yes;} nsurl * url = [request URL]; If ([[URL Scheme] isequaltostring: custom_protocol_scheme]) {// If ([url host] isequaltostring: queue_has_message]) in the queue {// fl the data in the queue [self _ flushmessagequeue];} else {nslog (@ "webviewjavascriptbridge: warning: received unknown webviewjavascriptbridge command % @: // % @ ", custom_protocol_scheme, [URL path]);} return no;} else if (self. webviewdelegate) {return [self. webviewdelegate webview: webview shouldstartloadwithrequest: Request navigationtype: navigationtype];} else {return YES ;}}

Sample UI

// Register a listenerdocument for the webviewjavascriptbridgeready event. addeventlistener ('webviewjavascriptbridgeready', onbridgeready, false) // function onbridgeready (event) {var bridge = event. bridgevar uniqueid = 1 // function log (message, data) {var log = document. getelementbyid ('log') var El = document. createelement ('div ') El. classname = 'logline' el. innerhtml = uniqueid ++ '. '+ message + (data? ':' + JSON. stringify (data): '') if (log. children. length) {log. insertbefore (El, log. children [0])} else {log. appendchild (EL) }}// initialize the message and define the default message processing logic bridge. init (function (Message) {log ('js got a message', message)}) // register a processor named testjavascripthandler and define the processing logic bridge for the response. registerhandler ('testjavascripthandler', function (data, response) {log ('js handler testjavascripthandler was called', data) response. Respondwith ({'javascript says ': 'right back atcha! '})} // Create a button for sending a message to native. var button = document. getelementbyid ('buttons '). appendchild (document. createelement ('button ') button. innerhtml = 'send message to objc' button. ontouchstart = function (e) {e. preventdefault () // send message bridge. send ('Hello from JS click')} document. body. appendchild (document. createelement ('br ') // create a button for calling the native processor var callbackbutton = document. getelementbyid ('buttons '). appendchild (document. createelement ('button ') callbackbutton. innerhtml = 'fire testobjccallback' callbackbutton. ontouchstart = function (e) {e. preventdefault () log ("calling handler testobjccallback") // call the native processor named testobjccallback, PASS Parameters, and set the callback processing logic bridge. callhandler ('testobjccallback', {'foo': 'bar'}, function (response) {log ('got response from testobjccallback', response )})}}

Native end

// Instantiate a webview and add it to the window to uiwebview * webview = [[uiwebview alloc] initwithframe: Self. window. bounds]; [self. window addsubview: webview]; // Enable logging [webviewjavascriptbridge enablelogging]; // instantiate webviewjavascriptbridge and define the default message processor _ bridge = [javasbridgeforwebview: webview handler: ^ (ID data, wvjbresponse * response) {nslog (@ "objc received message from JS: % @", data); uialertview * Alert = [[uialertview alloc] initwithtitle: @ "objc got message from javascript:" message: Data delegate: Nil cancelbuttontitle: @ "OK" otherbuttontitles: Nil]; [alert show] ;}]; // register a processor named testobjccallback for the UI to call and define the processing logic for the response. [_ bridge registerhandler: @ "testobjccallback" handler: ^ (ID data, wvjbresponse * response) {nslog (@ "testobjccallback called: % @", data); [Response respondwith: @ "response from Testo Bjccallback "] ;}]; // send a message to the UI end and define the callback processing logic [_ bridge send: @" a string sent from objc before webview has loaded. "responsecallback: ^ (ID error, Id responsedata) {If (error) {nslog (@" Uh oh-I got an error: % @ ", error );} nslog (@ "objc got response! % @ ", Error, responsedata) ;}]; // call a processor named testjavascripthandler defined on the UI end, with no callback defined [_ bridge callhandler: @ "testjavascripthandler" data: [nsdictionary dictionarywithobject: @ "before ready" forkey: @ "foo"]; [self renderbuttons: webview]; [self loadexamplepage: webview]; // send a message to the UI end [_ bridge send: @ "a string sent from objc after webview has loaded. "];

Project running:

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.