前陣子,Apple正式發佈了新的iOS 7系統,最大最直觀的改變在於界面變得小清新範了,我也提到《iOS,你真的愈來愈像Android了》。不過對於移動開發者來講,除了要適應Xcode 5,最應該關注的仍是iOS 7在開發接口的上的變化。概覽Apple提供的官方文檔《What’s New in iOS》,最最讓我欣喜的是iOS 7中加入了JavaScriptCore框架。該框架讓Objective-C和JavaScript代碼直接的交互變得更加的簡單方便。前端
JavaScriptCore程序員
這個框架其實只是基於webkit中以C/C++實現的JavaScriptCore的一個包裝,在舊版本iOS開發中,不少開發者也會自行將webkit的庫引入項目編譯使用。不過雖然iOS7把它當成了標準庫,惋惜目前,我尚未在Apple Developer中找到像以前文章中收集的那樣的官方文檔介紹這個框架的具體使用方法。web
好在還能夠在Xcode中找到頭文件,並且裏面的註釋對每一個類和方法的功能寫得還算清楚,再結合網上僅有的幾篇文章的介紹,我也在此簡單入門一下JavaScriptCore框架在基本數據類型上的使用。在另外一篇《JavaScriptCore框架在iOS7中的對象交互和管理》中,我更深刻地介紹該框架在對象上的使用。數組
JavaScriptCore中的類瀏覽器
在項目中引入JavaScriptCore後,鏈到頭文件中,除了大段的Copyright註釋能夠看到裏面只要引入了5個文件,每一個文件裏都定義跟文件名對應的類:app
JSContext框架
JSValueide
JSManagedValue函數
JSVirtualMachinethis
JSExport
JavaScriptCore_Head
雖然說代碼中的註釋介紹的也比較詳細了,可是如同一圖頂萬言,對程序員來講代碼更有說服力。本文就先來講說這些類相對比較好理解但又很經常使用的JSContext和JSValue以及它們方法的使用方式和效果。
JSContext和JSValue
JSVirtualMachine爲JavaScript的運行提供了底層資源,JSContext就爲其提供着運行環境,經過- (JSValue *)evaluateScript:(NSString *)script;方法就能夠執行一段JavaScript腳本,而且若是其中有方法、變量等信息都會被存儲在其中以便在須要的時候使用。而JSContext的建立都是基於JSVirtualMachine:- (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;,若是是使用- (id)init;進行初始化,那麼在其內部會自動建立一個新的JSVirtualMachine對象而後調用前邊的初始化方法。
JSValue則能夠說是JavaScript和Object-C之間互換的橋樑,它提供了多種方法能夠方便地把JavaScript數據類型轉換成Objective-C,或者是轉換過去。其一一對應方式可見下表:
Objective-C JavaScript JSValue Convert JSValue Constructor
nil undefined valueWithUndefinedInContext
NSNull null valueWithNullInContext:
NSString string toString
NSNumber number, boolean toNumber
toBool
toDouble
toInt32
toUInt32 valueWithBool:inContext:
valueWithDouble:inContext:
valueWithInt32:inContext:
valueWithUInt32:inContext:
NSDictionary Object object toDictionary valueWithNewObjectInContext:
NSArray Array object toArray valueWithNewArrayInContext:
NSDate Date object toDate
NSBlock Function object
id Wrapper object toObject
toObjectOfClass: valueWithObject:inContext:
Class Constructor object
基本類型轉換
先看個簡單的例子:
複製代碼
1 JSContext *context = [[JSContext alloc] init];
2 JSValue *jsVal = [context evaluateScript:@"21+7"];
3 int iVal = [jsVal toInt32];
4 NSLog(@"JSValue: %@, int: %d", jsVal, iVal);
5
6 //Output:
7 // JSValue: 28, int: 28
複製代碼
很簡單吧,還能夠存一個JavaScript變量在JSContext中,而後經過下標來獲取出來。而對於Array或者Object類型,JSValue也能夠經過下標直接取值和賦值。
複製代碼
1 JSContext *context = [[JSContext alloc] init];
2 [context evaluateScript:@"var arr = [21, 7 , 'iderzheng.com'];"];
3 JSValue *jsArr = context[@"arr"]; // Get array from JSContext
4
5 NSLog(@"JS Array: %@; Length: %@", jsArr, jsArr[@"length"]);
6 jsArr[1] = @"blog"; // Use JSValue as array
7 jsArr[7] = @7;
8
9 NSLog(@"JS Array: %@; Length: %d", jsArr, [jsArr[@"length"] toInt32]);
10
11 NSArray *nsArr = [jsArr toArray];
12 NSLog(@"NSArray: %@", nsArr);
13
14 //Output:
15 // JS Array: 21,7,iderzheng.com Length: 3
16 // JS Array: 21,blog,iderzheng.com,,,,,7 Length: 8
17 // NSArray: (
18 // 21,
19 // blog,
20 // "iderzheng.com",
21 // "<null>",
22 // "<null>",
23 // "<null>",
24 // "<null>",
25 // 7
26 // )
複製代碼
經過輸出結果很容易看出代碼成功把數據從Objective-C賦到了JavaScript數組上,並且JSValue是遵循JavaScript的數組特性:無下標越位,自動延展數組大小。而且經過JSValue還能夠獲取JavaScript對象上的屬性,好比例子中經過"length"就獲取到了JavaScript數組的長度。在轉成NSArray的時候,全部的信息也都正確轉換了過去。
方法的轉換
各類數據類型能夠轉換,Objective-C的Block也能夠傳入JSContext中當作JavaScript的方法使用。好比在前端開發中經常使用的log方法,雖然JavaScritpCore沒有自帶(畢竟不是在網頁上運行的,天然不會有window、document、console這些類了),仍然能夠定義一個Block方法來調用NSLog來模擬:
複製代碼
1 JSContext *context = [[JSContext alloc] init];
2 context[@"log"] = ^() {
3 NSLog(@"+++++++Begin Log+++++++");
4
5 NSArray *args = [JSContext currentArguments];
6 for (JSValue *jsVal in args) {
7 NSLog(@"%@", jsVal);
8 }
9
10 JSValue *this = [JSContext currentThis];
11 NSLog(@"this: %@",this);
12 NSLog(@"-------End Log-------");
13 };
14
15 [context evaluateScript:@"log('ider', [7, 21], { hello:'world', js:100 });"];
16
17 //Output:
18 // +++++++Begin Log+++++++
19 // ider
20 // 7,21
21 // [object Object]
22 // this: [object GlobalObject]
23 // -------End Log-------
複製代碼
經過Block成功的在JavaScript調用方法回到了Objective-C,並且依然遵循JavaScript方法的各類特色,好比方法參數不固定。也由於這樣,JSContext提供了類方法來獲取參數列表(+ (JSContext *)currentContext;)和當前調用該方法的對象(+ (JSValue *)currentThis)。對於"this",輸出的內容是GlobalObject,這也是JSContext對象方法- (JSValue *)globalObject;所返回的內容。由於咱們知道在JavaScript裏,全部全局變量和方法其實都是一個全局變量的屬性,在瀏覽器中是window,在JavaScriptCore是什麼就不得而知了。
Block能夠傳入JSContext做方法,可是JSValue沒有toBlock方法來把JavaScript方法變成Block在Objetive-C中使用。畢竟Block的參數個數和類型已經返回類型都是固定的。雖然不能把方法提取出來,可是JSValue提供了- (JSValue *)callWithArguments:(NSArray *)arguments;方法能夠反過來將參數傳進去來調用方法。
複製代碼
1 JSContext *context = [[JSContext alloc] init];
2 [context evaluateScript:@"function add(a, b) { return a + b; }"];
3 JSValue *add = context[@"add"];
4 NSLog(@"Func: %@", add);
5
6 JSValue *sum = [add callWithArguments:@[@(7), @(21)]];
7 NSLog(@"Sum: %d",[sum toInt32]);
8 //OutPut:
9 // Func: function add(a, b) { return a + b; }
10 // Sum: 28
複製代碼
JSValue還提供- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;讓咱們能夠直接簡單地調用對象上的方法。只是若是定義的方法是全局函數,那麼很顯然應該在JSContext的globalObject對象上調用該方法;若是是某JavaScript對象上的方法,就應該用相應的JSValue對象調用。
異常處理
Objective-C的異常會在運行時被Xcode捕獲,而在JSContext中執行的JavaScript若是出現異常,只會被JSContext捕獲並存儲在exception屬性上,而不會向外拋出。時時刻刻檢查JSContext對象的exception是否不爲nil顯然是不合適,更合理的方式是給JSContext對象設置exceptionHandler,它接受的是^(JSContext *context, JSValue *exceptionValue)形式的Block。其默認值就是將傳入的exceptionValue賦給傳入的context的exception屬性:
1 ^(JSContext *context, JSValue *exceptionValue) {
2 context.exception = exceptionValue;
3 };
咱們也能夠給exceptionHandler賦予新的Block以便在JavaScript運行發生異常的時候咱們能夠當即知道:
複製代碼
1 JSContext *context = [[JSContext alloc] init];
2 context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
3 NSLog(@"%@", exception);
4 con.exception = exception;
5 };
6
7 [context evaluateScript:@"ider.zheng = 21"];
8
9 //Output:
10 // ReferenceError: Can't find variable: ider
複製代碼
使用Block的注意事項
從以前的例子和介紹應該有體會到Block在JavaScriptCore中起到的強大做用,它在JavaScript和Objective-C之間的轉換 創建起更多的橋樑,讓互通更方便。可是要注意的是不管是把Block傳給JSContext對象讓其變成JavaScript方法,仍是把它賦給exceptionHandler屬性,在Block內都不要直接使用其外部定義的JSContext對象或者JSValue,應該將其當作參數傳入到Block中,或者經過JSContext的類方法+ (JSContext *)currentContext;來得到。不然會形成循環引用使得內存沒法被正確釋放。
好比上邊自定義異常處理方法,就是賦給傳入JSContext對象con,而不是其外建立的context對象,雖然它們實際上是同一個對象。這是由於Block會對內部使用的在外部定義建立的對象作強引用,而JSContext也會對被賦予的Block作強引用,這樣它們之間就造成了循環引用(Circular Reference)使得內存沒法正常釋放。
對於JSValue也不能直接從外部引用到Block中,由於每一個JSValue上都有JSContext的引用 (@property(readonly, retain) JSContext *context;),JSContext再引用Block一樣也會造成引用循環。