iOS7新JavaScriptCore架構介紹

來源:互聯網
上載者:User

這個架構其實只是基於webkit中以C/C++實現的JavaScriptCore的一個封裝,在舊版本iOS開發中,很多開發人員也會自行將webkit的庫引入項目編譯使用。不過雖然iOS7把它當成了標準庫,可惜目前,我還沒有在Apple Developer中找到像那樣的官方文檔介紹這個架構的具體使用方法。

JavaScriptCore中的類

在項目中引入JavaScriptCore後,鏈到標頭檔中,除了大段的Copyright注釋可以看到裡面只要引入了5個檔案,每個檔案裡都定義跟檔案名稱對應的類:

  • JSContext
  • JSValue
  • JSManagedValue
  • JSVirtualMachine
  • JSExport

    雖說代碼中的注釋介紹的也比較詳細了,但是如同一圖頂萬言,對程式員來說代碼更有說服力。本文就先來說說這些類相對比較好理解但又很常用的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: 316     // JS Array: 21,blog,iderzheng.com,,,,,7 Length: 817     // NSArray: (18     // 21,19     // blog,20     // "iderzheng.com",21     // "",22     // "",23     // "",24     // "",25     // 726     // )

    通過輸出結果很容易看出代碼成功把資料從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     // ider20     // 7,2121     // [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同樣也會形成引用迴圈。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.