iOS7新JavaScriptCore框架介紹

這個框架其實隻是基於webkit中以C/C++實現的JavaScriptCZ喎?/kf/ware/vc/” target=”_blank” class=”keylink”>vcmW1xNK7uPaw/Newo6zU2r7JsOaxvmlPU7+qt6LW0KOsuty24L+qt6LV39Kyu+HX1NDQvat3ZWJraXS1xL/i0v3I68/uxL+x4NLryrnTw6Gjsru5/cvkyLtpT1M3sNHL/LWxs8nBy7Hq17y/4qOsv8nPp8S/x7CjrM7Su7nDu9PQ1NpBcHBsZQogRGV2ZWxvcGVy1tDV0rW9z/HEx9H5tcS52be9zsS1tb3pydzV4rj2v/K83LXEvt/M5cq508O3vbeooaM8L3A+CjxwPjwvcD4KCgo8aDI+SmF2YVNjcmlwdENvcmXW0LXEwOA8L2gyPgo8cD4K1NrP7sS/1tDS/cjrSmF2YVNjcmlwdENvcmW686OswbS1vc23zsS8/tbQo6yz/cHLtPO2zrXEQ29weXJpZ2h016LKzb/J0tS/tLW9wO/D5ta70qrS/cjrwcs1uPbOxLz+o6zDv7j2zsS8/sDvtry2qNLluPrOxLz+w/u21NOmtcTA4KO6PC9wPgo8dWw+CjxsaT5KU0NvbnRleHQ8bGk+SlNWYWx1ZTxsaT5KU01hbmFnZWRWYWx1ZTxsaT5KU1ZpcnR1YWxNYWNoaW5lPGxpPkpTRXhwb3J0CjxwPgo8aW1nIGNsYXNzPQ==”size-full wp-image-655 alignright” src=”/uploadfile/Collfiles/20140410/20140410093556213.jpg” alt=”JavaScriptCore_Head” width=”459″ height=”280″>

雖說代碼中的註釋介紹的也比較詳細瞭,但是如同一圖頂萬言,對程序員來說代碼更有說服力。本文就先來說說這些類相對比較好理解但又很常用的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     // "",
22     // "",
23     // "",
24     // "",
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;讓我們可以直接簡單地調用對象上的方法。隻是如果定義的方法是全局函數,那麼很顯然應該在JSContextglobalObject對象上調用該方法;如果是某JavaScript對象上的方法,就應該用相應的JSValue對象調用。

異常處理

Objective-C的異常會在運行時被Xcode捕獲,而在JSContext中執行的JavaScript如果出現異常,隻會被JSContext捕獲並存儲在exception屬性上,而不會向外拋出。時時刻刻檢查JSContext對象的exception是否不為nil顯然是不合適,更合理的方式是給JSContext對象設置exceptionHandler,它接受的是^(JSContext
*context, JSValue *exceptionValue)
形式的Block。其默認值就是將傳入的exceptionValue賦給傳入的contextexception屬性:

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同樣也會形成引用循環。

發佈留言