SlideShare a Scribd company logo
JavaScriptCore
framework	
EZ-‐‑‒NET  熊⾕谷友宏    @es_̲kumagai
http://program.station.ez-‐‑‒net.jp/
の普通な使い⽅方
⾃自⼰己紹介
@es_̲kumagai	
EZ-‐‑‒NET  IP  Phone	
 ⾳音で再配達ゴッド	
⾳音で再配達	
 ⾳音でダイヤル	
 いつもの電卓
for  iPad	
いつもの電卓
for  iPhone	
EZ-‐‑‒NET  熊⾕谷友宏  	
http://program.station.ez-‐‑‒net.jp/
書籍
こんなご時世ですが
ぜひ⼿手に取ってパラパラめくってみてください。
•  Xcode  全機能を網羅羅
•  プロジェクトの作り⽅方
•  ソースコード編集の効率率率化
•  ショートカットキーの紹介
•  オートレイアウトの使い⽅方
•  ローカライズの設定⽅方法
•  バージョン管理理の使い⽅方
•  ビルド設定とスキーム設定
•  ほか、とにかくいろいろ
特設サイト  ̶—  http://ez-‐‑‒net.jp/sp/xcode5/
JavaScriptCore.framework
特徴
JavaScript  ⾔言語
1.  Web  でお馴染みのスクリプト⾔言語
2.  ⼿手軽にコードを組み⽴立立てられる
3.  JavaScript  を使える⼈人は多いはず
特徴
JavaScriptCore.framework
1.  アプリ内で  JavaScript  を実⾏行行可能
2.  ネイティブコードとの相互運⽤用が可能
OS  X  10.9、iOS  7.0  から利利⽤用可能
特徴
1.  アプリ内で  JavaScript  を実⾏行行可能
1.  スクリプトをテキストで⽤用意する
2.  実⾏行行する直前までに⽤用意すれば良良い
3.  ビルドに依らない実装が可能になる
    ➡  コードを⾃自由に差し替えられる
    ➡  リソースと同じように  DL  適⽤用できる
    ➡  アプリ使⽤用者にカスタムスクリプトを
        書かせる機能を容易易に実現できる
特徴
2.  ネイティブコードとの相互運⽤用
1.  変数の値を⾃自由に受け渡しできる
➡  ネイティブの⾃自作クラスも交換可能
2.  JavaScript  からネイティブコードの
メソッドを実⾏行行できる
3.  ネイティブコードから
JavaScript  の関数を実⾏行行できる
JavaScriptCore.framework
実⾏行行の流流れ
実⾏行行⼿手順  (1/4)
JavaScriptCore  をインポート
import JavaScriptCore
ターゲット設定の  Linked  Frameworks  and  Libraries  で
JavaScriptCore.framework  をリンクしておくこと
実⾏行行⼿手順  (2/4)
実⾏行行環境のコンテキストを⽣生成  
let context = JSContext()
このコンテキスト内で  JavaScript  を実⾏行行する
実⾏行行⼿手順  (3/4)
JavaScript  コードを実⾏行行
let script = "var value = encodeURI('<name>');"
context.evaluateScript(script)
実⾏行行結果はコンテキスト内に蓄積される
実⾏行行⼿手順  (4/4)
コンテキストから値を取得
let value:JSValue =
context.objectForKeyedSubscript("value")
println(value.toString())
コンテキスト内の値を  JSValue  型で取得できる
%3Cname%3E
JavaScriptCore.framework
実⾏行行⽅方法の詳細
実⾏行行⽅方法の詳細
JavaScript  コードを実⾏行行
JavaScript  コードを実⾏行行
JavaScript  コードの実⾏行行
context.evaluateScript(script) -> JSValue!
•  実⾏行行したい  JavaScript  を⽂文字列列で渡す
•  最後に実⾏行行した命令令の参照を受け取れる
–  最後が『value;』なら『value  の値』
–  最後が『10  +  3;』なら『13』
–  最後が『x  =  10;』なら代⼊入後の『x  の値』
–  最後が『var  x  =  10;』だと『undefined』
JavaScript  コードを実⾏行行
JavaScript  コンテキストから変数を取得
context.objectForKeyedSubscript(name)
-> JSValue!
•  取得したい変数名を⽂文字列列で渡す
•  変数の値を  JSValue  型で取得
–  toXXXX()  メソッドでネイティブ型に変換可能
–  指定した型に合わせて値が変換される
–  存在しない名前では  undefined  が得られる
※  Objective-‐‑‒C  なら  context[name]  で取得可能
JavaScript  コードを実⾏行行
•  toInt32() ->Int32
•  toUint32() ->Uint32
•  toDouble() ->Double
•  toString() ->String!
•  toBool() ->Bool
•  toObject() ->AnyObject!
•  toArray()    ->[AnyObject]!
•  toDictionary() ->[NSObject:AnyObject]!
•  toNumber() ->NSNumber!
•  toDate() ->NSDate!
•  toPoint() ->CGPoint
•  toSize() ->CGSize
•  toRect() ->CGRect
•  toRange() ->NSRange
JSValue  からネイティブ型に変換
JavaScript  コードを実⾏行行
JSValue  の未定義値をネイティブな値に変換
undefined
•  toString()
•  toInt32()
•  toDouble()
•  toBool()
•  toObject()
➡ "undefined"
➡ 0
➡ Double.NaN
➡ false
➡ nil
JavaScript  コードを実⾏行行
JSValue  の  null  値をネイティブな値に変換
null
•  toString()
•  toInt32()
•  toDouble()
•  toBool()
•  toObject()
➡ "null"
➡ 0
➡ 0.0
➡ false
➡ nil
JavaScript  コードを実⾏行行
•  isNumber()
•  isString()
•  isBoolean()
•  isObject()
•  isNull()
•  isUndefined()
JSValue  の型を判定する
実⾏行行⽅方法の詳細
JavaScript  に直接
変数を登録
JavaScript  に直接変数を登録
JavaScript  コンテキストに値を登録
context.setObject(value,forKeyedSubscript:name)
-> Void
•  設定したい変数名を⽂文字列列で渡す  (name)
–  存在しない名前の場合は新規登録する
–  登録済みの名前なら値を上書きする
•  設定する値を渡す  (value)
–  任意のネイティブ型を指定できる
–  JavaScript  はもともと  Variant  型
–  内部的には  [object  number]  や  [object  string]  等で認識識
–  nil  を渡すと  [object  undefined]  が設定される
JavaScript  に直接変数を登録
変数にネイティブオブジェクトも登録可能
詳細は後ほど
実⾏行行⽅方法の詳細
JavaScript  に直接
関数を登録
JavaScript  に直接関数を登録
Objective-‐‑‒C  で関数を登録する場合
context[@"sum"] = ^(NSArray* values)
{
NSInteger result = 0;
for (NSNumber* value in values)
{
result += value.integerValue;
}
return result;
};
変数の値として  Blocks  を渡すだけ  で  OK
JavaScript  に直接関数を登録
Swift  で関数を登録する場合  (1/6)
context.setObject(value,forKeyedSubscript:name)
-> Void
•  JSContext  は  Objective-‐‑‒C  クラス
•  Objective-‐‑‒C  では  id  型  で指定する
•  Swift  では  AnyObject!  型  で指定する
•  Swift  クロージャは  AnyObject!  に渡せない
JavaScript  に直接関数を登録
Swift  で関数を登録する場合  (2/6)
引数と戻り値が明⽰示的な  Blocks  引数には
Swift  のクロージャを渡せる
➡  JSContext  を  Objective-‐‑‒C  カテゴリ拡張して
    明⽰示的な  Blocks  を受け取るメソッドを作る
JavaScript  に直接関数を登録
Swift  で関数を登録する場合  (3/6)
#import <JavaScriptCore/JavaScriptCore.h>
typedef id (^unaryFunction)(id);
typedef id (^binaryFunction)(id, id);
@interface JSContext (Closure)
- (void)setUnaryFunction:(unaryFunction)function
forKeyedSubscript:(NSString*)key;
- (void)setBinaryFunction:(binaryFunction)function
forKeyedSubscript:(NSString*)key;
@end
JSContext+Closure.h
JavaScript  に直接関数を登録
Swift  で関数を登録する場合  (4/6)
- (void)setUnaryFunction:(unaryFunction)function
forKeyedSubscript:(NSString*)key
{
[self setObject:function forKeyedSubscript:key];
}
- (void)setBinaryFunction:(binaryFunction)function
forKeyedSubscript:(NSString*)key
{
[self setObject:function forKeyedSubscript:key];
}
JSContext+Closure.m
JavaScript  に直接関数を登録
Swift  で関数を登録する場合  (5/6)
#import "JSContext+Closure.h"
$(PROJECT_̲NAME)-‐‑‒Bridging-‐‑‒Header.h
このヘッダーをブリッジヘッダーにインポートして…
ブリッジヘッダーは  "Swift  Compiler  -‐‑‒  Code  Generation"  設定の
“Objective-‐‑‒C  Bridging  Header”  に登録されている
JavaScript  に直接関数を登録
Swift  で関数を登録する場合  (6/6)
let function = { (values:AnyObject!)->AnyObject in
var sum:Int = 0
for value in values as NSArray
{
sum += value.integerValue
}
return sum
}
context.setUnaryFunction(function,forKeyedSubscript:"sum")
これでクロージャを  JavaScript  へ登録可能に。
JavaScript  に直接関数を登録
登録した関数は  JavaScript  で普通に利利⽤用可能
context.evaluateScript("sum([10,20,30]);")
•  実⾏行行⽅方法は通常の  JavaScript  のとおり
•  結果の取得⽅方法は前述のとおり
実⾏行行⽅方法の詳細
複数⾏行行に渡る
JavaScript  コードの実⾏行行
複数⾏行行に渡る  JavaScript  コードの実⾏行行
Case  1:
ひとつの⽂文字列列にまとめて実⾏行行  #1
context.evaluateScript(
"var tag='<name>';n var val=encodeURI(tag);")
•  改⾏行行⽂文字が含まれていても実⾏行行可能
[OK]
複数⾏行行に渡る  JavaScript  コードの実⾏行行
Case  2:
ひとつの⽂文字列列にまとめて実⾏行行  #2
context.evaluateScript(
"var tag='<name>'; var val=nencodeURI(tag);")
•  JavaScript  として適切切であれば
コードの途中に改⾏行行⽂文字を挿⼊入可能
[OK]
複数⾏行行に渡る  JavaScript  コードの実⾏行行
Case  3:
各⾏行行を複数回に分けて実⾏行行  #1
context.evaluateScript(
"var tag='<name>';")
context.evaluateScript(
"var val=encodeURI(tag);")
•  実⾏行行結果はコンテキストに蓄積される
•  次の実⾏行行時に値を引き続き利利⽤用可能
[OK]
複数⾏行行に渡る  JavaScript  コードの実⾏行行
Case  4:
各⾏行行を複数回に分けて実⾏行行  #2
context.evaluateScript(
"var tag='<name>'; var val=")
context.evaluateScript(
"encodeURI(tag);")
•  ⾏行行の途中での  evaluateScript  はできない
•  SyntaxError:  Unexpected  EOF  例例外エラー
[NG]
複数⾏行行に渡る  JavaScript  コードの実⾏行行
Case  5:
各⾏行行を複数回に分けて実⾏行行  #3
context.evaluateScript("if (value==1)")
context.evaluateScript("{ (text="Yes") }")
context.evaluateScript("else")
context.evaluateScript("{ (text="No") }")
•  各⾏行行が独⽴立立して実⾏行行される
•  条件分岐が正しく⾏行行われない
•  ひとつの  evaluateScript  で実⾏行行すれば  OK
[NG]
複数⾏行行に渡る  JavaScript  コードの実⾏行行
Case  6:
各⾏行行を複数回に分けて実⾏行行  #4
context.evaluateScript("try {")
context.evaluateScript("value=XXXX;")
context.evaluateScript("} catch (e)")
context.evaluateScript(“{ value=0; }")
•  各⾏行行が独⽴立立して実⾏行行される
•  例例外が正しくハンドルされない
•  ひとつの  evaluateScript  で実⾏行行すれば  OK
[NG]
複数⾏行行に渡る  JavaScript  コードの実⾏行行
複数⾏行行の  JavaScript  は
意味的に不不⾜足のない単位で
実⾏行行すること
実⾏行行⽅方法の詳細
JavaScript  の
実⾏行行時エラーを検出
JavaScript  の実⾏行行時エラーを検出
evaluateScript  でエラーが発⽣生すると…
JavaScript  内で
例例外エラーが発⽣生する
JavaScript  の実⾏行行時エラーを検出
⼀一般的な  JavaScript  例例外オブジェクト
•  Error
•  SyntaxError
•  TypeError
•  EvalError
•  RangeError
•  ReferenceError
•  URIError
JavaScript  の実⾏行行時エラーを検出
JavaScript  内で発⽣生した例例外は
ネイティブコードで検出可能
•  コンテキストに  exceptionHandler  を登録
•  例例外が発⽣生すると関数が呼び出される
JavaScript  の実⾏行行時エラーを検出
JavaScript  例例外をネイティブコードで検出する
context.exceptionHandler
:((JSContext!,JSValue!)->Void)!
•  context:   実⾏行行した  JSContext  を取得
•  exception: 例例外オブジェクトを取得
JavaScript  例例外をネイティブコードで検出する
exceptionHandler  を登録
context.exceptionHandler = {
(context:JSContext!, exception:JSValue!)->Void in
println("Error: (exception.toString())")
};
JavaScript  例例外をネイティブコードで検出する
exception  から詳細情報を取得
•  .toString()
–  エラーメッセージ  を取得
–  "SyntaxError:  Expected  token  ':'"  など
•  .toDictionary()["line"] as? NSNumber
–  エラーが発⽣生した  ⾏行行番号  を取得
–  evaluateScript  に渡した⽂文字列列内での⾏行行番号
•  .toDictionary()["stack"] as? NSString
–  関数スタック  を取得する
–  改⾏行行⽂文字で区切切って関数名が記録される
–  構⽂文エラーなど、スタック情報がない場合は  nil
JavaScript  の実⾏行行時エラーを検出
exception  オブジェクトは
例例外  Error  オブジェクトそのもの
•  JavaScript  から  Error  例例外を送出
•  exceptionHandler  で受け取れる
•  エラーメッセージは  "Error:  message"
JavaScript  の実⾏行行時エラーを検出
JavaScript  からカスタムエラーを送出可能
context.evaluateScript("throw Error(message);")
•  独⾃自名の例例外を送出
•  エラーメッセージは  "MyError:  message"
JavaScript  の実⾏行行時エラーを検出
カスタムエラーの名称を指定可能
context.evaluateScript(
"var error=Error();" +
"error.name='MyError';" +
"error.message='message';" +
"throw error;")
JavaScriptCore.framework
ネイティブオブジェクトの利利⽤用
ネイティブオブジェクトの利利⽤用
利利⽤用⽅方法
1.  JavaScript  で使える機能を宣⾔言
2.  ネイティブオブジェクトを⽣生成
3.  JavaScript  からプロパティを参照
4.  JavaScript  からメソッドを実⾏行行
ネイティブオブジェクトの利利⽤用
JavaScript  で
使える機能を宣⾔言
JavaScript  で使える機能を宣⾔言
JSExport  を継承したプロトコルを作成
import JavaScriptCore
@objc protocol EZObjectJSExport: JSExport
{
var name:String { get set }
var value:String { get set }
func set(name:String, _ value:String)->Void
func toData()->NSData
}
@objc  指定⼦子を忘れないこと
JavaScript  で使える機能を宣⾔言
先ほどのプロトコルを継承したクラスを実装
public class EZObject: NSObject, EZObjectJSExport
{
public override init() { … }
public var name:String { … }
public var value:String { … }
public func set(name:String, _ value:String)->Void
{ … }
public func toData()->NSData { … }
}
必ず  NSObject  を継承すること
JavaScript  で使える機能を宣⾔言
オブジェクトの定義完了了
•  未定義のメソッドを呼び出すと
"TypeError:  'undefined'  is  not  a  function"
•  未定義のプロパティを呼び出すと  "undefined"
JSExport  を継承したプロトコル内で定義した
機能だけを  JavaScript  から  直接  利利⽤用できる
この辺りの挙動は  JavaScript  で「存在しないもの」を扱うのと同じ
ネイティブオブジェクトの利利⽤用
ネイティブオブジェクトの
インスタンスを作る
1.  ネイティブコードから⽣生成する⽅方法
2.  JavaScript  内で⽣生成する⽅方法
•  ネイティブコードで⽣生成したインスタンスを
コンテキストの  変数にそのまま登録
•  JavaScript  内から変数をとおして利利⽤用可能
ネイティブオブジェクトのインスタンスを作る
ネイティブコードから⽣生成する⽅方法
let object = EZObject()
context.setObject(object, forKeyedSubscript:"obj")
@objc protocol EZObjectJSExport: JSExport
{
class func create()->AnyObject
}
public class EZObject: NSObject, EZObjectJSExport
{
public class func create() -> AnyObject
{
return EZObject();
}
}
ネイティブオブジェクトのインスタンスを作る
JavaScript  内で⽣生成する⽅方法(追加準備)
インスタンスを⽣生成するクラスメソッドを追加
•  クラス情報をコンテキストに登録
•  クラスメソッドを使ってインスタンス⽣生成
ネイティブオブジェクトのインスタンスを作る
JavaScript  内で⽣生成する⽅方法(実装)
context.setObject(EZObject.self,
forKeyedSubscript:"EZObject")
context.evaluateScript("var obj=EZObject.create();")
•  ネイティブコードへの取り出しも可能
ネイティブオブジェクトのインスタンスを作る
JavaScript  内で⽣生成する⽅方法(余談)
let object = context.objectForKeyedSubscript("obj")
.toObject() as EZObject
ネイティブオブジェクトの利利⽤用
JavaScript  から
プロパティを使⽤用
•  プロパティ名の後には括弧不不要
•  JavaScript  どおりの⽅方法で読み書き可能
JavaScript  からプロパティを使⽤用
ネイティブコードのプロパティを読み書き
context.evaluateScript("var name = obj.name;")
context.evaluateScript("obj.value = 'NewValue';")
括弧をつけると  "Type  Error:  'PROP'  is  not  a  function"  エラー
ネイティブオブジェクトの利利⽤用
JavaScript  から
メソッドを実⾏行行
•  JavaScript  どおりの⽅方法で実⾏行行可能
•  引数を取らないメソッドも括弧が必要
JavaScript  からメソッドを実⾏行行
ネイティブコードのメソッドを実⾏行行
context.evaluateScript("obj.set('NewName','NewValue');")
context.evaluateScript("var data = obj.toData();")
括弧をつけないとメソッドそのものが得られる
ネイティブオブジェクトの利利⽤用
メソッド実装時の注意
JavaScript  からメソッドを実⾏行行
メソッド実装時の注意  #1
•  JavaScript  組み込みの  toString()  が優先
•  独⾃自に実装しても呼び出されない  (Beta  5)
toString()  メソッドは実装しない
JavaScript  からメソッドを実⾏行行
メソッド実装時の注意  #2
Swift  のメソッドは
引数のラベルが反映された名称になる
func set(name:String, value:String)->Void
➡ void setValue(name, value)
func set(#name:String, value:String)->Void
➡ void setWithNameValue(name, value)
func set(name:String, _ value:String)->Void
➡ void set(name, value)
JavaScriptCore.framework
相互運⽤用
JavaScript  とネイティブコード
JavaScript  とネイティブコードの相互運⽤用
JavaScript  関数を
ネイティブコードで実⾏行行
•  取得時は引数を添えずに関数名を指定する
–  JavaScript  関数の参照  を取得可能
•  実⾏行行時は引数を配列列で渡す
–  複数の引数を渡せる
–  今回は配列列を取る関数なので配列列を配列列に⼊入れている
JavaScript  関数をネイティブコードで実⾏行行
JavaScript  関数の取得と実⾏行行
context.evaluateScript("function sum(array) { … }")
let sum = context.objectForKeyedSubscript("sum")
let result = sum.callWithArguments([ [1,3,5,7] ])
JavaScript  とネイティブコードの相互運⽤用
JavaScript  オブジェクトと
ネイティブオブジェクトの相互運⽤用
オブジェクトの相互運⽤用
おさらい
ネイティブオブジェクトを  JavaScript  に取り込む場合
JavaScript  からネイティブオブジェクトを取得する場合
let object = EZObject()
context.setObject(object, forKeyedSubscript:"obj")
context.setObject(EZObject.self,
forKeyedSubscript:"EZObject")
context.evaluateScript("var obj=EZObject.create();")
let object = context.objectForKeyedSubscript("obj")
オブジェクトの相互運⽤用
どちらとも
相互にオブジェクトを操作可能
•  JavaScript  で設定した値を
直ぐに  ネイティブオブジェクトから利利⽤用可能
オブジェクトの相互運⽤用
JavaScript  での変更更がネイティブコードに反映
context.evaluateScript("obj.value = 'FromJS';")
println(object.value)
•  ネイティブオブジェクトで設定した値を
直ぐに  JavaScript  から利利⽤用可能
オブジェクトの相互運⽤用
ネイティブコードでの変更更が  JavaScript  に反映
object.value = "FromNative"
context.evaluateScript("var value=obj.value;")
let value = context.objectForKeyedSubscript("value")
println(value)
JavaScriptCore.framework
便便利利な使い⽅方
おまけ
便便利利な使い⽅方
スクリプトを
ファイルから読み込んで実⾏行行
var bundle = NSBundle.mainBundle()
var path = bundle.pathForResource("Script",ofType:"js")
let script = NSString(contentsOfFile:path,
encoding:NSUTF8StringEncoding, error: nil)
context.evaluateScript(script)
let result = context.objectForKeyedSubscript("answer")
スクリプトをファイルから読み込んで実⾏行行
バンドルからファイルを読み込む
スクリプトをリソースとして管理理できる
function sum(array)
{
var result = 0;
for (var i = 0; i < array.length; ++i)
{
result += array[i];
}
return result;
}
var answer = sum([1,10,100,1000]);
スクリプトをファイルから読み込んで実⾏行行
読み込む  JavaScript  ファイル
素のテキストとして扱えるので編集が簡単
便便利利な使い⽅方
return  命令令で
終われるスクリプトにする
function sum(array)
{
var result = 0;
for (var i = 0; i < array.length; ++i)
{
result += array[i];
}
return result;
}
return sum([1,10,100,1000]);
return  命令令で終われるスクリプトにする
読み込む  JavaScript  ファイル
最後を  return  命令令で終わらせたい
オブジェクトの相互運⽤用
SyntaxError:
Return  statements  are
only  valid  inside  functions
そのまま使うと…
return  命令令は関数内で使わなければいけない
return  命令令で終われるスクリプトにする
スクリプトを実⾏行行時に匿匿名関数で包む
let result =
context.evaluateScript("(function(){(script)})();")
•  スクリプトを関数内に⼊入れて関数を実⾏行行
•  return  の値は実⾏行行結果として取得可能
•  上記のとおり1⾏行行で記載すれば、
エラー時に通知される⾏行行番号が狂わない
JavaScriptCore.framework
•  JavaScript  は⼿手軽に使えるスクリプト⾔言語
•  JavaScript  コードをアプリ内で簡単に実⾏行行
•  ネイティブオブジェクトとの相互運⽤用が可能
•  JavaScript  ライブラリをネイティブコードで活⽤用
•  コンパイル不不要でスクリプトを差し替え可能
•  カスタムスクリプト機能を実装するのに便便利利
可能性を秘めたフレームワーク

More Related Content

JavaScriptCore.framework の普通な使い方 #cocoa_kansai