SwiftでJavaScript

案ずるより書くが易し…

Swiftのウリの一つは(Objective-)?Cに直アクセスできることですが、それが意味するのは、JavaScriptに直アクセスできるということでもあったのです。

## とりあえずJS使うだけなら

こんだけ。

```swift
import JavaScriptCore
let ctx = JSContext()
let ary = [0, 1, 2, 3]
var jsv = ctx.evaluateScript(
    "\(ary).map(function(n){return n*n})"
)
println(jsv)
var a = jsv.toArray()
println(a)
```

はい。見てのとおり、`import JavaScriptCore`して、`JSContext()`でJSの実行環境をこしらえて、それに`.evaluateScript()`で`String`食わせれば、おしまい。

実行結果は`JSValue`という型で、見ての通りObjective-Cに対応する型へ変更するメソッドもついてます。

## JSにSwiftの値を渡すには?

しかしこれだけではつまらない。Swiftの値をいちいち文字列化して`.evaluateScript()`のは面倒そう。直接JSの変数にアクセスすることはできないでしょうか?

そう。Objective-Cならね。

```objc
ctx[@"theAnswer"] = @42;
```

これが出来るなら、当然Swiftでも

```swift
ctx["theAnswer"] = 42
```

とかできそうなのですが、残念ながらObjective-Cのsubscriptまでは実装されていなかったようです。

というわけでbridgeを書きます。ここではプロジェクト名が`js`になっていますが、実際に使う際には各自読み替えてください。

js-Bridging-Header.h

```objc
#import <JavaScriptCore/JavaScriptCore.h>
JSValue *getJSVinJSC(JSContext *ctx, NSString *key);
void setJSVinJSC(JSContext *ctx, NSString *key, id val);
```

jsc.m

```objc
#import <JavaScriptCore/JavaScriptCore.h>
JSValue *getJSVinJSC(JSContext *ctx, NSString *key) {
    return ctx[key];
}
void setJSVinJSC(JSContext *ctx, NSString *key, id val) {
    ctx[key] = val;
}
```

あとは、

main.swift

```swift
extension JSContext {
    func get(key:NSString) -> JSValue {
        return getJSVinJSC(self, key)
    }
    func set(key:NSString, _ val:AnyObject) {
        setJSVinJSC(self, key, val)
    }
}
```

とSwiftでしておけば、

````swift
ctx.set("foo", [1,2,3])
jsv = ctx.evaluateScript("bar = foo.map(function(n){return n*n})")
println(ctx.get("bar")) // [1, 4, 9]
````

とあいなります。めでたしめでたし…?

## JSにSwiftのクロージャを渡すには?

しかし残念ながら、Objective-Cの`id`、Swiftの`AnyObject`は、関数までは扱ってくれないのです。

```swift
ctx.set("add", { (x:Double,y:Double) in x + y }) // error
```

「(Double,Double)->DoubleはAnyObjectではありませんっ」って怒られちゃいます。

さあ、どうする?

結局こうしました。ちょっと泥臭い方法ですが、ほとんどのシーンでこれで間に合うはずです。

js-Bridging-Header.h に追加

```objc
void setB0JSVinJSC(JSContext *ctx, NSString *key,id(^block)());
void setB1JSVinJSC(JSContext *ctx, NSString *key,id(^block)(id));
void setB2JSVinJSC(JSContext *ctx, NSString *key,id(^block)(id,id));
```

jsc.m に追加

```objc
void setB0JSVinJSC(
    JSContext *ctx, NSString *key, id (^block)()
) {
    ctx[key] = block;
}
void setB1JSVinJSC(
    JSContext *ctx, NSString *key, id (^block)(id)
) {
    ctx[key] = block;
}
void setB2JSVinJSC(
    JSContext *ctx, NSString *key, id (^block)(id, id)
){
    ctx[key] = block;
}
```

Swiftに追加

```swift
extension JSContext {
    func set(key:NSString, _ blk:(()->AnyObject!)?) {
        setB0JSVinJSC(self, key, blk)
    }
    func set(key:NSString, _ blk:((AnyObject!)->AnyObject!)?) {
        setB1JSVinJSC(self, key, blk)
    }
    func set(key:NSString, _ blk:((AnyObject!,AnyObject!)->AnyObject!)?) {
        setB2JSVinJSC(self, key, blk)
    }
}
```

`.set`が複数あることに着目。こうすることで、単一のメソッドで通常の値も関数も扱えます。

要するに、引数が0,1,2個の場合にそれぞれbridgeするというわけです。

```swift
// block w/ no argument
ctx.set("hello") { ()->AnyObject! in
    return "Hello, JS! I am Swift."
}
println(ctx.evaluateScript("hello"))
println(ctx.evaluateScript("hello()"))
```

```swift
// block w/ 1 argument
ctx.set("square") { (o:AnyObject!)->AnyObject! in
    if let x = o as? Double {
        return x * x
    }
    return nil
}
println(ctx.evaluateScript("square"))
println(ctx.evaluateScript("square()"))
println(ctx.evaluateScript("square(6)"))
```

```swift
// block w/ 2 arguments
ctx.set("multiply") { (o:AnyObject!, p:AnyObject!)->AnyObject! in
    if let x = o as? Double {
        if let y = p as? Double {
            return x * y
        }
    }
    return nil
}
println(ctx.evaluateScript("multiply"))
println(ctx.evaluateScript("multiply()"))
println(ctx.evaluateScript("multiply(6)"))
println(ctx.evaluateScript("multiply(6,7)"))
```

見ての通りAnyObjectゆえ型チェックと変換の手間は避けられませんが、それを差し引いてもあっけにとられるほど簡単です。

三つ以上の値を渡したいなら、`Array`を経由した方が楽でしょう。

```swift
// for any more arguments, just use array instead
ctx.set("sum") { (o:AnyObject!)->AnyObject! in
    if let a = o as? NSArray {
        // since the NSArray elements are also AnyObject,
        // we can't reduce() 
        var result = 0.0
        for v in a {
            if let n = v as? Double {
                result += n
            } else {
                return nil
            }
        }
        return result
    }
    return nil
}
println(ctx.evaluateScript("sum([0,1,2,3,4,5,6,7,8,9])"))
````

以上の結果は、Githubにもおいときます。

* https://github.com/dankogai/swift-jsdemo

もっとエレガントな方法を見つけたという方は、ぜひご報告を。

Enjoy!

Dan the Polyglot