• Like
  • Save
  • Private Content
JavaScriptCore.framework の普通な使い方 #cocoa_kansai
Upcoming SlideShare
Loading in...5
×
 

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

on

  • 1,462 views

Objective-C や Swift のネイティブコードから JavaScript をランタイムで実行するための JavaScriptCore.framework ...

Objective-C や Swift のネイティブコードから JavaScript をランタイムで実行するための JavaScriptCore.framework のお話です。基本的な機能の説明と、注意点を整理して紹介しています。

Statistics

Views

Total Views
1,462
Views on SlideShare
1,462
Embed Views
0

Actions

Likes
1
Downloads
3
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

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

    • 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  ライブラリをネイティブコードで活⽤用 •  コンパイル不不要でスクリプトを差し替え可能 •  カスタムスクリプト機能を実装するのに便便利利 可能性を秘めたフレームワーク