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);

}

相关文章
相关标签/搜索