2016년 10월 17일 월요일

JavascriptCore in Swift 3 연습

//: Playground - noun: a place where people can play

import UIKit
import JavaScriptCore

/* swift to javascript */
let str = "var double=function(x) { return x*2 };"
let context = JSContext()

context?.evaluateScript(str)
let getDouble = context?.objectForKeyedSubscript("double")
getDouble?.call(withArguments: [1]).toString()

*2를 하는 double이라는 function을 js에서 만들고 swift에서 인지 1을 넣어 2를 반환하는 예.
스위프트에서 자바스크립트 함수를 호출할 때는 
  1. objectForKeyedSubscript를 사용하여 함수명을 지정하고
  2. call(withArguments: [, ...]) 를 사용하여 인자를 지정한다.
  3. 결과값은 JSValue 형이므로 적당히 변환해서 사용한다.

/* javascript to swfit */
let str2 = "postMessage('갔다가 다시 올꺼야')"
let postMessageBlock : @convention(block) (AnyObject?) -> (AnyObject) = { (data) in
    print(data)
    return data!
}
context?.globalObject.setValue(unsafeBitCast(postMessageBlock, to: AnyObject.self), forProperty: "postMessage")

context?.evaluateScript(str2)

반대의 경우, 즉 자바스크립트에서 스위프트의 특정 함수를 호출하고자 할 땐
해당 context의 globalObject에 setValue를 하되 unsafeBitCast를 사용한다.
evaluateScript를 한 후 js의 postMessage는 setValue가 연결한 swift쪽의 postMessageBlock을 호출.
들어온 값은 콘솔에 출력하고 자기 자신을 그대로 반환하도록 하였다.

요정도만 있으면 일단 Javascript기반 script 엔진을 만드는데 문제는 없어보인다.

Android의 경우 https://github.com/ericwlange/AndroidJSCore 를 사용 중인데

아주 좋은 예제가 있으니 이쪽을 참조하자.

* 10/25 추가분 *
Object안에 function이 있을 경우 objectForKeyedSubscript로 접근이 안되는데
context?.objectForKeyedSubscript("objS.s")
가령 이런 건 의도대로 잘 되지 않는다.

context?.evaluateScript("var someObj = { a: 1, b: 2, c: function(v) { return v*v; } }")
context?.evaluateScript("someObj.c").call(withArguments: [10]).toInt32()

이와 같이 해당 key를 evaluateScript해서 직접 call을 사용하면 접근할 수 있다.
그런데 이런 경우 문제는 단지 function(v) { return v*v; } 만 가져오는 것이므로 function 안에서 다른 멤버변수인 a,b를 접근할 수 없다.

* 10/28 추가분 *
만일 자바스크립트 쪽에서 인자로 무기명 함수를 사용할 경우 해당 인자를 JSValue로 받아 evaluateScript 하는 형식으로 사용할 수 있다.
다만 해당 함수는 바로 사용할 수 없으므로 IIFE(https://en.wikipedia.org/wiki/Immediately-invoked_function_expression)로 감싸서 바로 실행할 수 있도록 만들어 줘야한다.
let publishBlock: @convention(block) (AnyObject?, JSValue?) -> (AnyObject) = { (data, callback) in
    print("(\(callback!.toString()!))()")
    return (context?.evaluateScript(callback?.toString()))!
}
context?.globalObject.setValue(unsafeBitCast(publishBlock, to: AnyObject.self), forProperty: "Meteor_publish")
context?.evaluateScript("var q=0; Meteor_publish('something', function() { q= 100; })")
context?.evaluateScript("q")