JavaScriptCore in iOS

原文鏈接

可參考的文章:http://blog.csdn.net/longshihua/article/details/51645575

JSContext / JSValue

JSContext 是運行 JavaScript 代碼的環境。一個 JSContext 是一個全局環境的實例,如果你寫過一個在瀏覽器內運行的 JavaScript,JSContext 類似於 window。創建一個 JSContext 後,可以很容易地運行 JavaScript 代碼來創建變量,做計算,甚至定義方法:

SwiftObjective-C
JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"var num = 5 + 5"];
[context evaluateScript:@"var names = ['Grace', 'Ada', 'Margaret']"];
[context evaluateScript:@"var triple = function(value) { return value * 3 }"];
JSValue *tripleNum = [context evaluateScript:@"triple(num)"];

代碼的最後一行,任何出自 JSContext 的值都被包裹在一個 JSValue 對象中。像 JavaScript 這樣的動態語言需要一個動態類型,所以 JSValue 包裝了每一個可能的 JavaScript 值:字符串和數字;數組、對象和方法;甚至錯誤和特殊的 JavaScript 值諸如 null 和 undefined

JSValue 包括一系列方法用於訪問其可能的值以保證有正確的 Foundation 類型,包括:

JavaScript Type JSValue method Objective-C Type Swift Type
string toString NSString String!
boolean toBool BOOL Bool
number toNumber
toDouble
toInt32
toUInt32
NSNumber
double
int32_t
uint32_t
NSNumber!
Double
Int32
UInt32
Date toDate NSDate NSDate!
Array toArray NSArray [AnyObject]!
Object toDictionary NSDictionary [NSObject : AnyObject]!
Object toObject
toObjectOfClass:
custom type custom type

從上面的例子中得到 tripleNum 的值,只需使用適當的方法:

SwiftObjective-C
NSLog(@"Tripled: %d", [tripleNum toInt32]);
// Tripled: 30

下標值

對 JSContext 和 JSValue 實例使用下標的方式我們可以很容易地訪問我們之前創建的 context 的任何值。JSContext 需要一個字符串下標,而 JSValue 允許使用字符串或整數標來得到裏面的對象和數組:

SwiftObjective-C
JSValue *names = context[@"names"];
JSValue *initialName = names[0];
NSLog(@"The first name: %@", [initialName toString]);
// The first name: Grace

Swift 展示了它的青澀,在這裏,Objective-C 代碼可以利用下標表示法,Swift 目前只公開原始方法來讓下標成爲可能:objectAtKeyedSubscript() 和 objectAtIndexedSubscript()

調用方法

JSValue 包裝了一個 JavaScript 函數,我們可以從 Objective-C / Swift 代碼中使用 Foundation 類型作爲參數來直接調用該函數。再次,JavaScriptCore 很輕鬆的處理了這個橋接:

SwiftObjective-C
JSValue *tripleFunction = context[@"triple"];
JSValue *result = [tripleFunction callWithArguments:@[@5] ];
NSLog(@"Five tripled: %d", [result toInt32]);

錯誤處理

JSContext 還有另外一個有用的招數:通過設置上下文的 exceptionHandler 屬性,你可以觀察和記錄語法,類型以及運行時錯誤。 exceptionHandler 是一個接收一個 JSContext 引用和異常本身的回調處理:

SwiftObjective-C
context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
   NSLog(@"JS Error: %@", exception);
};

[context evaluateScript:@"function multiply(value1, value2) { return value1 * value2 "];
// JS Error: SyntaxError: Unexpected end of script

JavaScript 調用

現在我們知道了如何從 JavaScript 環境中提取值以及如何調用其中定義的函數。那麼反向呢?我們怎樣才能從 JavaScript 訪問我們在 Objective-C 或 Swift 定義的對象和方法?

讓 JSContext 訪問我們的本地客戶端代碼的方式主要有兩種:block 和 JSExport 協議。

Blocks

當一個 Objective-C block 被賦給 JSContext 裏的一個標識符,JavaScriptCore 會自動的把 block 封裝在 JavaScript 函數裏。這使得在 JavaScript 中可以簡單的使用 Foundation 和 Cocoa 類,所有的橋接都爲你做好了。見證了 CFStringTransform 的強大威力,現在讓我們來看看 JavaScript:

SwiftObjective-C
context[@"simplifyString"] = ^(NSString *input) {
   NSMutableString *mutableString = [input mutableCopy];
   CFStringTransform((__bridge CFMutableStringRef)mutableString, NULL, kCFStringTransformToLatin, NO);
   CFStringTransform((__bridge CFMutableStringRef)mutableString, NULL, kCFStringTransformStripCombiningMarks, NO);
   return mutableString;
};

NSLog(@"%@", [context evaluateScript:@"simplifyString('안녕하새요!')"]);

在這兒,Swfit 還有一個坑,請注意,這僅適用於 Objective-C 的 block,而不是 Swift 的閉包。要在 JSContext 中使用 Swift 閉包,它需要(a)與 @ objc_block 屬性一起聲明,以及(b)使用 Swift 那個令人恐懼的 unsafeBitCast() 函數轉換爲 AnyObject

內存管理

由於 block 可以保有變量引用,而且 JSContext 也強引用它所有的變量,爲了避免強引用循環需要特別小心。避免保有你的 JSContext 或一個 block 裏的任何 JSValue。相反,使用 [JSContext currentContext] 得到當前上下文,並把你需要的任何值用參數傳遞。

JSExport 協議

另一種在 JavaScript 代碼中使用我們的自定義對象的方法是添加 JSExport 協議。無論我們在 JSExport 裏聲明的屬性,實例方法還是類方法,繼承的協議都會自動的提供給任何 JavaScript 代碼。我們將在下一節看到。

JavaScriptCore 實戰

讓我們做一個使用了所有這些不同的技術的示例 - 我們將定義一個 Person 模型符合 JSExport 子協議 PersonJSExports 的例子,然後使用 JavaScript 從 JSON 文件中創建並填充實例。都有一個完整的 JVM 在那兒了,誰還需要 NSJSONSerialization

1) PersonJSExports 和 Person

我們的 Person 類實現了 PersonJSExports 協議,該協議規定哪些屬性在 JavaScript 中可用。

由於 JavaScriptCore 沒有初始化,所以 create... 類方法是必要的,我們不能像原生的 JavaScript 類型那樣簡單地用 var person = new Person()

SwiftObjective-C
// in Person.h -----------------
@class Person;

@protocol PersonJSExports <JSExport>
    @property (nonatomic, copy) NSString *firstName;
    @property (nonatomic, copy) NSString *lastName;
    @property NSInteger ageToday;

    - (NSString *)getFullName;

    // create and return a new Person instance with `firstName` and `lastName`
    + (instancetype)createWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;
@end

@interface Person : NSObject <PersonJSExports>
    @property (nonatomic, copy) NSString *firstName;
    @property (nonatomic, copy) NSString *lastName;
    @property NSInteger ageToday;
@end

// in Person.m -----------------
@implementation Person
- (NSString *)getFullName {
    return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}

+ (instancetype) createWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
    Person *person = [[Person alloc] init];
    person.firstName = firstName;
    person.lastName = lastName;
    return person;
}
@end

2) JSContext 配置

之前,我們可以用我們已經創建的 Person 類,我們需要將其導出到 JavaScript 環境。我們也將藉此導入 Mustache JS library,我們將應用模板到我們的 Person 對象。

// export Person class
context[@"Person"] = [Person class];

// load Mustache.js
NSString *mustacheJSString = [NSString stringWithContentsOfFile:... encoding:NSUTF8StringEncoding error:nil];
[context evaluateScript:mustacheJSString];

3) JavaScript 數據和進程

下面就來看看我們簡單的 JSON 例子,這段代碼將創建新的 Person 實例。

注意:JavaScriptCore 轉換的 Objective-C / Swift 方法名是 JavaScript 兼容的。由於 JavaScript 沒有參數 名稱,任何外部參數名稱都會被轉換爲駝峯形式並且附加到函數名後。在這個例子中,Objective-C 的方法 createWithFirstName:lastName: 變成了在JavaScript中的 createWithFirstNameLastName()

JavaScriptJSON
var loadPeopleFromJSON = function(jsonString) {
    var data = JSON.parse(jsonString);
    var people = [];
    for (i = 0; i < data.length; i++) {
        var person = Person.createWithFirstNameLastName(data[i].first, data[i].last);
        person.birthYear = data[i].year;

        people.push(person);
    }
    return people;
}

4) 加到一起

剩下的就是加載 JSON 數據,調用 JSContext 將數據解析成 Person 對象的數組,並用 Mustache 模板呈現每個 Person

SwiftObjective-C
// get JSON string
NSString *peopleJSON = [NSString stringWithContentsOfFile:... encoding:NSUTF8StringEncoding error:nil];

// get load function
JSValue *load = context[@"loadPeopleFromJSON"];
// call with JSON and convert to an NSArray
JSValue *loadResult = [load callWithArguments:@[peopleJSON]];
NSArray *people = [loadResult toArray];

// get rendering function and create template
JSValue *mustacheRender = context[@"Mustache"][@"render"];
NSString *template = @"{{getFullName}}, born {{birthYear}}";

// loop through people and render Person object as string
for (Person *person in people) {
   NSLog(@"%@", [mustacheRender callWithArguments:@[template, person]]);
}

// Output:
// Grace Hopper, born 1906
// Ada Lovelace, born 1815
// Margaret Hamilton, born 1936
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章