Objective-c interaction with js

Source: Internet
Author: User

Objective-c interaction with js
1 Principle

When writing JavaScript, you can use an object called window. For example, when we want to jump from the current webpage to another webpage, We will modify the window. location. href position; in our Objective-C program code, if we can get the specified WebView object, we can get the window object that appears in JavaScript, that is, [webView windowScriptObject].

This object is the bridge between JS in WebView and our Objective-C program-the window object can obtain all JS functions and objects in the webpage, if we set an Objective-C object to the value of windowScriptObject, JS can also call the method of the Objective-C object. Therefore, we can require WebView to execute a JS section in the Objective-C program, or in turn let JS call a function implemented by Obj C.

Because Objective-C and JS have different language features, the differences between the two languages can be seen:

  • JS Is OO, but there is no class, so the JS object will be uploaded to the Obj C program, except the basic string will be converted to NSString, the basic number will be converted to NSNumber, such as Array and other objects, in Objective-C, all are WebScriptObject Class. That is, the Array of JS will not help you convert it into NSArray. Upload an empty object from JS to the Objective-C program. It does not use the method that originally represents "nothing" in Objective-C, such as NULL, nil, and NSNull, it is the WebUndefined used by the exclusive WebKit. 2. Tips 2.1 use Objective-C to obtain and set JavaScript objects

    To obtain the JavaScript Object in the webpage from Objective-C, that is, to make some KVC calls to windowScriptObject, such as valueForKey: And valueForKeyPath :. If we want to know the current webpage location in JS, We will write it like this:

    Var location = window. location. href;

    Objective-C can be called as follows:

    NSString * location = [[webView windowScriptObject] valueForKeyPath: @ location. href];

    If we want to set window. location. href, we need to open another webpage in JS:

    Window. location. href = 'HTTP: // spring-studio.net ';

    In Objective-C:

    [[WebView windowScriptObject] setValue: @ http://spring-studio.netforKeyPath: @ location. href];

    2.2 Use Objective C to call JavaScript function2.2.1 use evaluateWebScript to execute

    There are several methods to call JS functions on a webpage using Objective-C. The first is to directly write a program that is exactly the same as the JS program you will write on the web page, called windowScriptObject using evaluateWebScript: Execute.

    For example, if we want to generate a new JS function on a webpage, the content is:

    Function x (x ){

    Return x + 1;

    }

     

    Therefore, this can be written in Objective-C;

    [[WebView windowScriptObject] evaluateWebScript: @ function x (x) {return x + 1 ;}];

    Next we can call window. x ():

    NSNumber * result = [[webView windowScriptObject] evaluateWebScript: @ x (1)];

    NSLog (@ result: % d, [result integerValue]); // Returns 2

    2.2.2 use the function object WebScriptObject to execute itself

    In JS, every funciton is actually an object, so we can also directly obtain window. x to call this object to execute ourselves.

    Write in JS as follows:

    Window. x. call (window. x, 1 );

    In Objective-C, this is the case:

    WebScriptObject * x = [[webView windowScriptObject] valueForKey: @ x];

    NSNumber * result = [x callWebScriptMethod: @ call withArguments: [NSArray arrayWithObjects: x, [NSNumbernumberWithInt: 1], nil];

     

    In this way, a WebScriptObject will not be used to call the JS end from Objective-C, but will be mentioned later. JS calls Objective-C, in this way, JS can send a callback function to the Objective-C program.

     

    2.2.3 use DOM objects

    In WebKit, all DOM objects are inherited from DOMObject, and DOMObject is inherited from WebScriptObject. Therefore, after obtaining a DOM object, we can also use the Objective-C program, this DOM object is required to execute JS programs.

    Assume that there is a text input box (text input) with the id "# s" in our webpage, and we want the keyboard input to focus on this input box, write in JS as follows:

    Document. querySelector ('# s'). focus ();

    Write in Objective-C:

    DOMDocument * document = [[webView mainFrame] DOMDocument];

    [[Document querySelector: @ # s] callWebScriptMethod: @ focuswithArguments: nil];

     

    2.3 use JavaScript to access the Value of Objective-C

    To enable javascript programs on the webpage to call the Objective-C object, register an Objective-C object as the properties of the window object in JS. Then, JS can also call the method of this object, or obtain various values of this object, as long as it is the Value that KVC can obtain, such as NSString, NSNumber, NSDate, NSArray, NSDictionary, NSValue... . When JS uploads an Array to Objective-C, it also requires some special processing to convert it into NSArray. When it transfers an NSArray from Obj C to JS, it will automatically become a JS Array.

    2.3.1 when the Objective-C object is registered to the window object

    The first thing we should note is the time to register the Objective-C object to the window object, the content of the window object will change-after all, each page will have a different JS program, so we need to do this at the right time. First, we need to specify the frame loading delegate (using setFrameLoadDelegate :) of WebView, and implement webView: didClearWindowObject: forFrame:. WebView calls this program as long as windowScriptObject is updated.

    If we want to allow JS in the webpage to use the current controller object, we will write as follows:

    -(Void) webView :( WebView *) sender didClearWindowObject :( WebScriptObject *) windowObject forFrame :( WebFrame *) frame

    {

    [WindowObject setValue: self forKey: @ controller];

    }

    In this way, we can call our Objective-C object by calling window. controller.

    2.3.2 Access the Value of the Objective-C object in JS

    Suppose our Objective-C Class contains these member variables:

    @ Interface MyController: NSObject

    {

    IBOutlet WebView * webView;

    IBOUtlet NSWindow * window;

    NSString * stringValue;

    NSInteger numberValue;

    NSArray * arrayValue;

    NSDate * dateValue;

    NSDictionary * dictValue;

    NSRect frameValue;

    }

    @ End

     

    Specify the following Value:

    StringValue = @ string;

    NumberValue = 24;

    ArrayValue = [[NSArray arrayWithObjects: @ text, [NSNumbernumberWithInt: 30], nil] retain];

    DateValue = [[NSDate date] retain];

    DictValue = [[NSDictionary dictionaryWithObjectsAndKeys: @ value1, @ key1, @ value2, @ key2, @ value3, @ key3, nil] retain];

    FrameValue = [window frame];

     

    Read it in JS:

    Var c = window. controller;

    Var main = document. getElementById ('main ');

    Var HTML = '';

    If (c ){

    HTML + ='

    '+ C. stringValue +'

    ';

    HTML + ='

    '+ C. numberValue +'

    ';

    HTML + ='

    '+ C. arrayValue +'

    ';

    HTML + ='

    '+ C. dateValue +'

    ';

    HTML + ='

    '+ C. dictValue +'

    ';

    HTML + ='

    '+ C. frameValue +'

    ';

    Main. innerHTML = HTML;

    }

    The result is as follows:

    String 24 text, 30 2010-09-09 00:01:04 + 0800 {key1 = value1; key2 = value2; key3 = value3;} NSRect: {275, 72}, {570,657 }}

    However, if you have read the above example, you can simply do it. Instead of directly producing the correct result, you will get a bunch of undefined results. The reason is, the Value preset of the Objective-C object is protected and won't be directly accessed by JS. To allow JS to access the Value of the Objective-C object, the operation + isKeyExcludedFromWebScript is required to process the Input Key one by one. If we want JS to access this key, NO will be returned:

    + (BOOL) isKeyExcludedFromWebScript :( const char *) name

    {

    If (! Strcmp (name, stringValue )){

    Return NO;

    }

    Return YES;

    }

    In addition to reading the Value of the Objective-C object, you can also set the Value, which is equivalent to using setValue: forKey: in Objective-C. If we want to modify stringValue In the JS program above, directly call c. stringValue = 'new value. As mentioned above, the JS objects passed to Objective-C here, except for strings and numbers, all classes are WebScriptObject, And the empty object is WebUndefined.

    2.4 differences in the way in which JavaScript calls the Objective-C method 2.4.1

    The syntax of Objective-C follows the selector of SmallTalk and Objective-C, which is quite different from the function Syntax of JS. WebKit's default practice is that if we want to call Objective-C selector in JS, we will put all the parameters behind and change all the colons to the bottom line, if the selector has a bottom line, it must be processed separately.

    If our controller object has a method, write it in Objective-C as follows:

    -(Void) setA :( id) a B :( id) B c :( id) c;

    This is called in JS:

    Controller. setA_ B _c _ ('A', 'B', 'C ');

    2.4.2 alias for a method

    It's really ugly. Therefore, WebKit provides a method that enables us to turn an Objective-C selector into a nice JS function. We want to implement webScriptNameForSelector:

    + (NSString *) webScriptNameForSelector :( SEL) selector

    {

    If (selector = @ selector (setA: B: c :)){

    Return @ setABC;

    }

    Return nil;

    }

    This can be called later:

    Controller. setABC ('A', 'B', 'C ');

    We can also decide which selector can be used for JS and which can be protected by implementing isSelectorExcludedFromWebScript :. However, we can change the name of an Objective-C selector in JS. We can also change the key of a value by implementing webScriptNameForKey :.

    2.4.3 note

    Note the following:

    Use JavaScript to call the property of Objective C 2.0. In the above example, we use JS to call window. controller. stringValue. When we set the value in it, this is like we use the Objective-C 2.0 syntax, but what we actually do is different. Use JS to call controller. stringValue. The corresponding Objective-C syntax is [controller valueForKey: @ stringValue], rather than calling the property of the Objective-C object.

     

    If our Objective-C object has a property called stringValue, we know that the Objective-C property will actually become getter/setter method during compilation, in JS, we should call controller. stringValue () and controller. setStringValue _().

     

    In Javascript, Function is the feature of objects.

    JS functions are objects. When the method of an Objective-C object appears in JS, this method can be processed more or less as an object. We have produced setABC on it. You can also try to pour it out:

    Console. log (controller. setABC );

    We can see from the results:

     

    Function setABC () {[native code]}

    This function is native code. Because it is native code, we cannot call or apply this function.

     

    In addition, register our Objective-C object as a window. after the controller, we may also want to convert the controller into a function for execution, such as calling window. controller (); or, we only want to generate a function that can be called by JS, rather than putting the entire object into JS. As long as invokedefamethomethodwitharguments: is implemented in the Objective-C object, the expected results can be returned when window. controller () is called.

    2.4.4 example

    Now we can perform a comprehensive exercise. As mentioned above, since we can pass the JS object to the Obj C program using the class WebScriptObject, the Objective-C program can also require the execution of various functions of WebScriptObject. If we want to drop the numbers A and B into the Objective-C program and add them to the webpage, we write an Objective-C method:

    -(Void) numberWithA :( id) a plusB :( id) B callback :( id) callback

    {

    NSInteger result = [a integerValue] + [B integerValue];

    [Callback callWebScriptMethod: @ call withArguments: [NSArrayarrayWithObjects: callback, [NSNumber numberWithInteger: result], nil];

    }

    In JS, you can call it like this:

    Window. controller. numberWithA_plusB_callback _ (1, 2, function (result ){

    Var main = document. getElementById ('main ');

    Main. innerText = result;

    });

    3. WebViewJavascriptBridge

    WebViewJavascriptBridge

    Https://github.com/marcuswestin/WebViewJavascriptBridge

     

    3.1 mechanism Principle

    Obviously: WebViewJavascriptBridge.js.txt is mainly used to link web pages in UIWebView, while WebViewJavascriptBridge. h/m is mainly used to deal with ObjC nativecode. 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 Nativecode 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.

    3.1.1 Native working mechanism

    In Native, the main working mechanism is to intercept the Delegate of the current WebView, and use the current Bridge instance object as the Target object to Delegate, after obtaining the WebViewDelegate callback method (mainly in the shouldStartLoadWithRequest callback method) and processing it, callback to the target VC. The Code is as follows:

    -(Void) _ platformSpecificSetup :( WVJB_WEBVIEW_TYPE *) webView webViewDelegate :( id ) WebViewDelegate handler :( WVJBHandler) messageHandler resourceBundle :( NSBundle *) bundle {

    _ MessageHandler = messageHandler;

    _ WebView = webView;

    _ WebViewDelegate = webViewDelegate;

    _ MessageHandlers = [NSMutableDictionary dictionary];

    _ WebView. delegate = self;

    _ ResourceBundle = bundle;

    }

     

    Callback upper-layer method example:

    _ Strong WVJB_WEBVIEW_DELEGATE_TYPE * strongDelegate = _ webViewDelegate;

    If (strongDelegate & [strongDelegate respondsToSelector: @ selector (webViewDidFinishLoad :)]) {

    [StrongDelegate webViewDidFinishLoad: webView];

    }

     

    3.1.2 Native-side event parsing and Processing Mechanism

    The business data to be transmitted on the Js end is not transmitted through the Url parameter, but is directly obtained by calling the js method on the Native end, and then parsed. It is concentrated in the-(void) _ flushMessageQueue method. The core code is as follows:

     

    // 1. Obtain the JSON object string of business data

    NSString * messageQueueString = [_ webView stringByEvaluatingJavaScriptFromString: @ WebViewJavascriptBridge. _ fetchQueue ();];

    // 2. serialize an object into an array

    Id messages = [self _ deserializeMessageJSON: messageQueueString];

    // 3. enumerate and parse each Message object

    NSString * responseId = message [@ responseId];

    If (responseId ){

    WVJBResponseCallback responseCallback = _ responseCallbacks [responseId];

    ResponseCallback (message [@ responseData]);

    [_ 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;

    If (message [@ handlerName]) {

    Handler = _ messageHandlers [message [@ handlerName];

    } Else {

    Handler = _ messageHandler;

    }

     

    ///!!! Complete event registration callback here, including registration events and receiving data events

    Handler (message [@ data], responseCallback );

    }

     

    3.1.3 js Working Mechanism

    Js uses iFrame to trigger a load action, but the Url of iFrame does not carry data, but is only used to trigger the load action. The specific business data is cached in the sendMessageQueue array.

    After the Native captures the load action, it calls the _ fetchQueue () method of the window. WebViewJavascriptBridge class to obtain the business data for parsing.

    Therefore, js has three main functions:

    • Cache business data (including registration events); provides public methods for Native to call, which is generally used to obtain cached business data; triggers the load action to wake up interaction with Native;

       

      Main public methods:

      HandleMessageFromObjC Method

      Used to send data to the js end by Native;

       

      FetchQueue method:

      Used for Native to obtain business data objects;

       

      RegisterHandler (handlerName, handler) Method

      Registers js event methods for Native calls;

      CallHandler (handlerName, data, responseCallback) Method

      Used to call the Native event method.

      3.2 features

      [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 the Native end to the UI end, and support the definition of callback processing logic after the UI end responds;

      (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 (for UI calls), and supports defining the UI end and Native end of the response processing logic for the UI end. The implementation is also equivalent. 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

      3.3 Use Process

      1. initialize a webview (viewdidload)

      UIWebView * webView = [[UIWebView alloc] initWithFrame: self. view. bounds];

      [Self. view addSubview: webView];

      2. associate this webview with WebViewJavascriptBridge (viewdidload)

      If (_ bridge) {return ;}

      [WebViewJavascriptBridge enableLogging];

      _ Bridge = [WebViewJavascriptBridge bridgeForWebView: webView webViewDelegate: self handler: ^ (id data, WVJBResponseCallback responseCallback ){

      NSLog (@ ObjC received message from JS: % @, data );

      ResponseCallback (@ Response for message from ObjC );

      }];

      Ps: Now your webview is connected to js. The following describes the mutual transfer of methods and their parameters.

      (1) js oc adjustment method (you can pass a value to the oc method through data, and then return the value to js using responseCallback)

      [_ Bridge registerHandler: @ testObjcCallback handler: ^ (id data, WVJBResponseCallback responseCallback ){

      NSLog (@ testObjcCallback called: % @, data );

      ResponseCallback (@ Response from testObjcCallback );

      }];

      Note the testObjcCallback method. The name of html must be the same as that of ios. Of course, this name can be customized on both sides. Simple and clear.

      (2) Adjust js methods in oc (data can be used to pass values and response can be used to accept the return values in js)

      Id data =@{@ greetingFromObjC: @ Hi there, JS! };

      [_ Bridge callHandler: @ testJavascriptHandler data: data responseCallback: ^ (id response ){

      NSLog (@ testJavascriptHandler responded: % @, response );

      }];

      Note that testJavascriptHandler is also a method identifier.

      (3) passing oc values to js (using response to accept returned values)

      [_ Bridge send: @ A string sent from ObjC to JS responseCallback: ^ (id response ){

      NSLog (@ sendMessage got response: % @, response );

      }];

      (4) passing oc values to js (no return value)

      [_ Bridge send: @ A string sent from ObjC after Webview has loaded.];

      3.4 initialize sample code with 3.4.1 on JS end

      Function connectWebViewJavascriptBridge (callback ){

      If (window. WebViewJavascriptBridge ){

      Callback (WebViewJavascriptBridge)

      } Else {

      // Annotate the initialization method as an event listener

      Document. addEventListener ('webviewjavascriptbridgeready', function (){

      Callback (WebViewJavascriptBridge)

      }, False)

      }

      }

       

      ConnectWebViewJavascriptBridge (function (bridge ){

      Var uniqueId = 1

      Function log (message, data ){

      Var log = document. getElementById ('log ')

      Var el = document. createElement ('div ')

      El. className = 'logline'

      El. innerHTML = uniqueId ++ '.' + message + ':
      '+ JSON. stringify (data)

      If (log. children. length) {log. insertBefore (el, log. children [0])}

      Else {log. appendChild (el )}

      }

      // Call the Init Initialization Method of the object to complete the initialization of the Bridge object itself

      Bridge. init (function (message, responseCallback ){

      Log ('js got a message', message)

      Var data = {'javascript responds': 'wee! '}

      Log ('js responding with ', data)

      ResponseCallback (data)

      })

      // Register some events for Native to call

      Bridge. registerHandler ('testjavascripthandler', function (data, responseCallback ){

      Log ('objc called testJavascriptHandler with ', data)

      Var responseData = {'javascript Says ': 'right back atcha! '}

      Log ('js responding with', responseData)

      ResponseCallback (responseData)

      })

      })

      3.4.2 register an event for Native call

      // Register some events for Native to call

      Bridge. registerHandler ('testjavascripthandler', function (data, responseCallback ){

      Log ('objc called testJavascriptHandler with ', data)

      Var responseData = {'javascript Says ': 'right back atcha! '}

      Log ('js responding with', responseData)

      ResponseCallback (responseData)

      })

      3.4.3 send data to Native through UI events

      Var button = document. getElementById ('buttons'). appendChild (document. createElement ('button '))

      Button. innerHTML = 'send message to objc'

      Button. onclick = function (e ){

      E. preventDefault ()

      Var data = 'Hello from JS click'

      Log ('js sending message', data)

      Bridge. send (data, function (responseData ){

      Log ('js got response', responseData)

      })

      }

      Document. body. appendChild (document. createElement ('br '))

      3.4.4 call the Native Registration Method for UI events

      Var callbackButton = document. getElementById ('buttons'). appendChild (document. createElement ('click '))

      CallbackButton. innerHTML = 'fire testobjccallback'

      CallbackButton. onclick = function (e ){

      E. preventDefault ()

      Log ('js calling handler testobjccallback ')

      Bridge. callHandler ('testobjccallback', {'foo': 'bar'}, function (response ){

      Log ('js got response', response)

      })

      }

      3.5 Native end uses 3.5.1 for initialization

      We recommend that you complete

      _ Bridge = [WebViewJavascriptBridge bridgeForWebView: webView webViewDelegate: self handler: ^ (id data, WVJBResponseCallback responseCallback ){

      NSLog (@ ObjC received message from JS: % @, data );

      ResponseCallback (@ Response for message from ObjC );

      }];

      3.5.2 register the event Method for JS call

      [_ Bridge registerHandler: @ testObjcCallback handler: ^ (id data, WVJBResponseCallback responseCallback ){

      NSLog (@ testObjcCallback called: % @, data );

      ResponseCallback (@ Response from testObjcCallback );

      }];

      3.5.3 send data to JS end

      [_ Bridge send: @ A string sent from ObjC before Webview has loaded. responseCallback: ^ (id responseData ){

      NSLog (@ objc got response! % @, ResponseData );

      }];

      3.5.4 call methods in JS

      [_ Bridge callHandler: @ testJavascriptHandler data :@{@ foo: @ before ready}];

      4 Native + h5 page APP business solution imagine 4.1 js end call APP Method

      WebViewJavascriptBridge supports actively sending data and calling Native registered event methods to interact with Native.

      The two mechanisms have their own advantages. However, to ensure backward compatibility, we recommend that you use the method of actively sending data to achieve interaction with the Native end. Abstract and classify the services on the UI end as much as possible, encapsulate them into JSON objects in the form of command code + sub-command code + parameter data, and pass them to the Native end, the parsing logic of Native uses an engine class for centralized processing.

      This also facilitates the logical unification of IOS and Android platforms.

      Take integrated payment as an example:

      Because the payment SDK can only use native interfaces, you can consider abstracting various js-side payment operations (such as initiating payment and receiving coupons) into different types of commands, when you click the "pay" button on the page, the h5 page sends the payment command to the Native end. After Native parses the command, it calls the payment interface in the SDK to complete the payment, after obtaining the payment result, call back the result to the h5 page.

      Because such apps are mainly information-based Apps, The h5 page will be used for all kinds of product display pages, but when it comes to interaction with Native terminals, A unified bridge layer is required to handle all kinds of business operations. Instead of using a third-party library that encapsulates specific Native functions, the general design idea should be to design a unified bridge layer, then, we can encapsulate all the Native-side capabilities in a unified manner. In this respect, this is the case, and so is the case of Meiju.

      4.2 The APP calls the js method

      When the APP calls the js method, we recommend that you use the same policy to develop a mutual call specification, specify the command encoding, and use json objects to transmit object data. However, the App can provide some registration methods for the js terminal to obtain general information of the APP, such as device information and size screens.

       

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.