iOS js與objective-c的交互(轉)

在寫 JavaScript 的時候,可使用一個叫作 window 的對象,像是咱們想要從如今的網頁跳到另一個網頁的時候,就會去修改 window.location.href 的位置;在咱們的 Objective-C 程序碼中,若是咱們能夠取得指定的 WebView 的指標,也就能夠拿到這個出如今 JavaScript 中的 window 對象,也就是 [webView windowScriptObject]。web

 

這個對象就是 WebView 裏頭的 JS 與咱們的 Objective-C程序之間的橋樑-window 對象能夠取得網頁裏頭全部的 JS 函數與對象,而若是咱們把一個 Objective-C 對象設定成 windowScriptObject 的 value,JS 也即可以調用Objective-C對象的 method。因而,咱們能夠在Objective-C 程序裏頭要求 WebView 執行一段 JS,也能夠反過來讓 JS 調用一段用 Obj C 實做的功能。spring

 

※ 用Objective-C 取得與設定JavaScript 對象app

 

要從 Objective-C取得網頁中的 JavaScript 對象,也就是對 windowScriptObject 作一些 KVC 調用,像是 valueForKey: 與 valueForKeyPath:。若是咱們在 JS 裏頭,想要知道目前的網頁位置,會這麼寫:函數

  var location = window.location.href;this

用 Objective-C 就能夠這麼調用:lua

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

 

若是咱們要設定 window.location.href,要求開啓另一個網頁,在 JS 裏頭:設計

  window.location.href = 'http://spring-studio.net';code

在Objective-C:server

  [[webView windowScriptObject] setValue:@"http://spring-studio.net"forKeyPath:@"location.href"];

 

因爲Objective-C 與 JS 自己的語言特性不一樣,在兩種語言之間相互傳遞東西之間,就能夠看到二者的差異:

  • JS 雖然是 OO,可是並無 class,因此將 JS 對象傳到 Obj C 程序裏頭,除了基本字串會轉換成 NSString、基本數字會轉成 NSNumber,像是 Array 等其餘對象,在 Objective-C 中,都是 WebScriptObject 這個 Class。意思就是,JS 的 Array 不會幫你轉換成 NSArray。

  • 從 JS 裏頭傳一個空對象給 Objective-C 程序,用的不是 Objective-C 裏頭本來表示「沒有東西」的方式,像是 NULL、nil、NSNull 等,而是專屬 WebKit 使用的 WebUndefined。

 

因此,若是咱們想要看一個 JS Array 裏頭有什麼東西,就要先取得這個對象裏頭叫作 length 的 value,而後用 webScriptValueAtIndex: 去看在該 index 位置的內容。

假如咱們在 JS 裏頭這樣寫:

 

var JSArray = {'zonble', 'dot', 'net'};

for (var i = 0; i < JSArray.length; i++) {

  console.log(JSArray[i]);

}

 

在Objective-C 裏頭就會變成這樣:

 

WebScriptObject *obj = (WebScriptObject *)JSArray;

NSUInteger count = [[obj valueForKey:@"length"] integerValue];

NSMutableArray *a = [NSMutableArray array];

for (NSUInteger i = 0; i < count; i++) {

    NSString *item = [obj webScriptValueAtIndex:i];

    NSLog(@"item:%@", item);

}

 

※ 用Objective C 調用 JavaScript function

 

要用 Objective-C 調用網頁中的 JS function,大概有幾種方法。第一種是直接寫一段跟你在網頁中會撰寫的 JS 如出一轍的程序,叫 windowScriptObject 用 evaluateWebScript: 執行。

例如,咱們想要在網頁中產生一個新的 JS function,內容是:

 

function x(x) {

    return x + 1;

}

 

因此在 Objective-C 中能夠這樣寫;

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

接下來咱們就能夠調用 window.x():

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

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

 

因爲在 JS 中,每一個 funciton 其實都是對象,因此咱們還能夠直接取得 window.x 叫這個對象執行本身。

在 JS 裏頭若是這樣寫:

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

Objective-C 中即是這樣:

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

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

 

這種讓某個 WebScriptObject 本身執行本身的寫法,其實比較不會用於從 Objective-C 調用 JS 這一端,而是接下來會提到的,由 JS 調用 Objective-C,由於這樣 JS 就能夠把一個 callback function 送到 Objective-C 程序裏頭。

 

若是咱們在作網頁,咱們只想要更新網頁中的一個區塊,就會利用 AJAX 的技巧,只對這個區塊須要的資料,對 server 發出 request,而且在 request 完成的時候,要求執行一段 callback function,更新這一個區塊的顯示內容。從 JS 調用 Objective-C也能夠作相似的事情,若是 Objective-C程序裏頭須要必定時間的運算,或是咱們多是在 Objective-C 裏頭抓取網路資料,咱們即可以把一個 callback function 送到 Objective-C程序裏,要求Objective-C程序在作完工做後,執行這段 callback function。

 

DOM

 

WebKit 裏頭,全部的 DOM 對象都繼承自 DOMObject,DOMObject 又繼承自 WebScriptObject,因此咱們在取得了某個 DOM 對象以後,也能夠從 Objective-C 程序中,要求這個 DOM 對象執行 JS 程序。

假如咱們的網頁中,有一個 id 叫作 「#s」 的文字輸入框(text input),而咱們但願如今鍵盤輸入的焦點放在這個輸入框上,在 JS 裏頭會這樣寫:

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

在Objective-C中寫法:

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

[[document querySelector:@"#s"] callWebScriptMethod:@"focus"withArguments:nil];

 

※ 用 JavaScript 存取 Objective C 的 Value

 

要讓網頁中的 JS 程序能夠調用 Objective-C 對象,方法是把某個 Objective-C 對象註冊成 JS 中 window 對象的屬性。以後,JS 便也能夠調用這個對象的 method,也能夠取得這個對象的各類 Value,只要是 KVC 能夠取得的 Value,像是 NSString、NSNumber、NSDate、NSArray、NSDictionary、NSValue…等。JS 傳 Array 到 Objective-C 時,還須要特別作些處理才能變成 NSArray,從 Obj C 傳一個 NSArray 到 JS 時,會自動變成 JS Array。

 

首先咱們要注意的是將 Objective-C 對象註冊給 window 對象的時機,因爲每次從新載入網頁,window 對象的內容都會有所變更-畢竟每一個網頁都會有不一樣的 JS 程序,因此,咱們須要在適當的時機作這件事情。咱們首先要指定 WebView 的 frame loading delegate(用 setFrameLoadDelegate:),而且實做 webView:didClearWindowObject:forFrame:,WebView 只要更新了 windowScriptObject,就會調用這一段程序。

假如咱們如今要讓網頁中的 JS 可使用目前的 controller 對象,會這樣寫:

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

{

    [windowObject setValue:self forKey:@"controller"];

}

如此一來,只要調用 window.controller,就能夠調用咱們的 Objective-C 對象。假如咱們的 Objective-C Class 裏頭有這些成員變數:

@interface MyController : NSObject

{

    IBOutlet WebView *webView;

    IBOUtlet  NSWindow *window;

    NSString *stringValue;

    NSInteger numberValue;

    NSArray *arrayValue;

    NSDate *dateValue;

    NSDictionary *dictValue;

    NSRect frameValue;

}

@end

指定一下 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];

 

用 JS 讀讀看:

var c = window.controller;

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

var HTML = '';

if (c) {

    HTML += '<p>' + c.stringValue + '<p>';

    HTML += '<p>' + c.numberValue + '<p>';

    HTML += '<p>' + c.arrayValue + '<p>';

    HTML += '<p>' + c.dateValue + '<p>';

    HTML += '<p>' + c.dictValue + '<p>';

    HTML += '<p>' + c.frameValue + '<p>';

    main.innerHTML = HTML;

}

結果以下:

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

 

不過,若是你看完上面的範例,就直接照作,應該不會直接成功出現正確的結果,而是會拿到一堆 undefined,緣由是,Objective-C 對象的 Value 預設被保護起來,不會讓 JS 直接存取。要讓 JS 能夠存取 Objective-C 對象的 Value,須要操做 +isKeyExcludedFromWebScript: 針對傳入的 Key 一一處理,若是咱們但願 JS 能夠存取這個 key,就回傳 NO:

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

{

    if (!strcmp(name, "stringValue")) {

        return NO;

    }

    return YES;

}

除了能夠讀取 Objective-C對象的 Value 外,也能夠設定 Value,至關於在 Objective-C中使用 setValue:forKey:,若是在上面的 JS 程序中,咱們想要修改 stringValue,直接調用 c.stringValue = ‘new value’ 便可。像前面提到,在這裡傳給 Objective-C的 JS 對象,除了字串與數字外,class 都是 WebScriptObject,空對象是 WebUndefined。

 

 JavaScript調用 Objective C method

 

Objective-C 的語法沿襲自 SmallTalk,Objective-C 的 selector,與 JS 的 function 語法有至關的差別。WebKit 預設的實事是,若是咱們要在 JS 調用 Objective-C selector,就是把全部的參數日後面擺,而且把全部的冒號改爲底線,而原來 selector 若是有底線的話,又要另外處理。

假使咱們的 controller 對象有個 method,在 Objective-C 中寫成這樣:

- (void)setA:(id)a b:(id)b c:(id)c;

在 JS 中就這麼調用:

controller.setA_b_c_('a', 'b', 'c');

 

實在有點醜。因此 WebKit 提供一個方法,可讓咱們把某個 Objective-C selector 變成好看一點的 JS function。咱們要實做 webScriptNameForSelector:

+ (NSString *)webScriptNameForSelector:(SEL)selector

{

    if (selector == @selector(setA:b:c:)) {

        return @"setABC";

    }

    return nil;

}

之後就能夠這麼調用:

controller.setABC('a', 'b', 'c');

 

咱們一樣能夠決定哪些 selector 能夠給 JS 使用,哪些要保護起來,方法是實做 isSelectorExcludedFromWebScript:。而咱們能夠改變某個 Objective-C selector 在 JS 中的名稱,咱們也能夠改變某個 value 的 key,方法是實做 webScriptNameForKey:。

 

有幾件事情須要注意一下:

用 JavaScript 調用 Objective C 2.0 的 property

在上面,咱們用 JS 調用 window.controller.stringValue,與設定裏頭的 value 時,這邊很像咱們使用 Objective-C 2.0 的語法,但其實作的是不同的事情。用 JS 調用 controller.stringValue,對應到的 Objective-C 語法是 [controller valueForKey:@"stringValue"],而不是調用 Objective-C 對象的 property。

 

若是咱們的 Objective-C 對象有個 property 叫作 stringValue,咱們知道,Objective-C property 其實會在編譯時,變成 getter/setter method,在 JS 裏頭,咱們便應該要調用 controller.stringValue() 與 controller.setStringValue_()。

 

Javascript 中,Function 即對象的特性

JS 的 function 是對象,當一個 Objective-C 對象的 method 出如今 JS 中時,這個 method 在 JS 中,也能夠或多或少當作對象處理。咱們在上面產生了 setABC,也能夠試試看把它倒出來瞧瞧:

console.log(controller.setABC);

咱們能夠從結果看到:

function setABC() { [native code] }

這個 function 是 native code。由於是 native code,因此咱們沒法對這個 function 調用 call 或是 apply。

 

另外,在把咱們的 Objective-C 對象註冊成 window.controller 後,咱們會許也會想要讓 controller 變成一個 function 來執行,像是調用 window.controller();或是,咱們就只想要產生一個可讓 JS 調用的 function,而不是整個對象都放進 JS 裏頭。咱們只要在 Objective-C 對象中,實做 invokeDefaultMethodWithArguments:,就能夠回傳在調用 window.controller() 時想要的結果。

 

前面提到,因爲咱們能夠把 JS 對象以 WebScriptObject 這個 class 傳入 Obj C 程序,Objective-C 程序中也能夠要求執行 WebScriptObject 的各項 function。咱們假如想把 A 與 B 兩個數字丟進 Objective-C 程序裏頭作個加法,加完以後出如今網頁上,因而咱們寫了一個 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]];

}

JS 裏頭就能夠這樣調用:

window.controller.numberWithA_plusB_callback_(1, 2,function(result) {

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

    main.innerText = result;

});

 

其餘平臺上 WebKit的用法

 

除了 Mac OS X,WebKit 也慢慢移植到其餘的做業系統與 framework 中,也或多或少都有 Native API 要求 WebView 執行 Js,以及從 JS 調用 Native API 的機制。

 

跟 Mac OS X 比較起來,IOS 上 UIWebView 的公開 API 實在少上許多。想要讓 UIWebView 執行一段 JS,能夠透過調用 stringByEvaluatingJavaScriptFromString:,只會回傳字串結果,因此可以作到的事情也就變得有限,一般大概就拿來取得像 window.title 這些資訊。在 IOS 上咱們沒辦法將某個 Objective-C 對象變成 JS 對象,因此,在網頁中觸發了某些事件,想要通知 Objective-C 這一端,每每會選擇使用像「zonble://」這類 Customized URL scheme。

 

ChromeOS 徹底以 WebKit 製做使用者介面,不過咱們沒辦法在 ChomeOS 上寫咱們在這邊所討論的桌面或行動應用程序,因此不在咱們討論之列。(順道岔題,ChromeOS 是設計來給 Netbook 使用的做業系統,但是像 Toshiba 都已經用 Android,作出比 Netbook 更小的 Smartbook,並且應用程序更多,ChromeOS 的產品作出來的話,實在很像 Google 拿出兩套東西,本身跟本身對打)。

 

Android 的 WebView 對象提供一個叫作 addJavascriptInterface() 的 method,能夠將某個 Java 對象註冊成 JS 的 window 對象的某個屬性,就可讓 JS 調用 Java 對象。不過,在調用 Java 對象時,只可以傳遞簡單的文字、數字,複雜的 JS 對象就沒辦法了。而在 Android 上想要 WebView 執行一段 JS,在文件中沒看到相關資料,網路上面找到的說法是,能夠透過 loadUrl(),把某段 JS 用 bookmarklet 的形式傳進去。

 

在 QtWebKit 裏頭,能夠對 QWebFrame 調用 addToJavaScriptWindowObject,把某個 QObject 暴露在 JS 環境中,我不清楚 JS 能夠傳遞哪些東西到 QObject 裏頭就是了。在 QtWebKit 中也能夠取得網頁裏頭的 DOM 對象(QWebElement
、QWebElementCollection),咱們能夠對 QWebFrame 還有這些 DOM 對象調用 evaluateJavaScript,執行 Javascript。

GTK 方面,由於是 C API,因此在應用程序與 JS 之間,就不是透過操做包裝好的對象,而是調用 WebKit 裏頭 JavaScript Engine 的 C API。

 

 JavaScriptCore Framework

 

咱們在 Mac OS X 上面,也能夠透過 C API,要求 WebView 執行 Javascript。首先要 import 。若是咱們想要簡單改一下 window.location.href:

JSGlobalContextRef globalContext = [[webView mainFrame] globalContext];

JSValueRef exception = NULL;

JSStringRef script = JSStringCreateWithUTF8CString("window.location.href='http://spring-studio.net'");

JSEvaluateScript(globalContext, script, NULL, NULL, 0, &exception);

JSStringRelease(script);

 

若是咱們想要讓 WebView 裏頭的 JS,能夠調用咱們的 C Function:

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

{

    JSGlobalContextRef globalContext = [frame globalContext];

    JSStringRef name = JSStringCreateWithUTF8CString("myFunc");

    JSObjectRef obj = JSObjectMakeFunctionWithCallback(globalContext, name, (JSObjectCallAsFunctionCallback)myFunc);

    JSObjectSetProperty (globalContext, [windowObject JSObject], name, obj, 0, NULL);

    JSStringRelease(name);

}

那麼,只要 JS 調用 window.myFunc(),就能夠取得們放在 myFunc 這個 C function 中回傳的結果:

JSValueRef myFunc(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)

{

    return JSValueMakeNumber(ctx, 42);

}

相關文章
相關標籤/搜索