讓你的iOS應用程序支持運行JavaScript腳本:JavaScriptCore框架詳解

讓你的iOS應用程序支持運行JavaScript腳本:JavaScriptCore框架詳解

    說到JavaScript腳本,iOS開發者都會想到一個名叫JavaScriptCore的框架。這個框架的確十分強大,其中封裝了一套JavaScript運行環境以及Native與JS數據類型之間的轉換橋樑。本篇博客主要討論如何使用此框架來在iOS應用中運行JavaScript腳本。javascript

1、JavaScriptCore框架結構

    在學習一個框架時,首先應該先了解整個框架的結構,拿iOS開發來舉例,對於一個陌生的框架,第一步須要先搞清楚這裏面都包含哪些類,個各種之間是怎樣的關係,這個框架和其餘的框架間有無聯繫以及怎樣產生的聯繫。將些問題搞清楚,有了大致上的認識後,咱們再來學習其中的每一個類即其餘細節的應用將很是容易。咱們先來看一張JavaScriptCore框架的結構圖:前端

這張圖是我手工畫的,不是那麼美觀而且沒有文字的解釋,可是我以爲它能很是直觀的表達JavaScriptCore中包含的類之間的關係。下面我來向你解釋這張圖究竟表達了什麼意思,首先原生的iOS應用是支持多線程執行任務的,咱們知道JavaScript是單線程,但這並不表明咱們不能在Native中異步執行不一樣的JavaScript代碼。java

1.JSVirtualMachine——JavaScript的虛擬機android

    JavaScriptCore中提供了一個名爲JSVirtualMachine的類,顧名思義,這個類能夠理解爲一個JS虛擬機。在Native中,只要你願意,你能夠建立任意多個JSVirtualMachine對象,各個JSViretualMachine對象間是相互獨立的,他們之間不能共享數據也不能傳遞數據,若是你把他們放在不一樣的Native線程,他們就能夠並行的執行無關的JS任務。git

2.JSContext——JavaScript運行環境github

    JSContext上下文對象能夠理解爲是JS的運行環境,同一個JSVirtualMachine對象能夠關聯多個JSContext對象,而且在WebView中每次刷新操做後,此WebView的JS運行環境都是不一樣的JSContext對象。其做用就是用來執行JS代碼,在Native和JS間進行數據的傳遞。編程

3.JSValue——JavaScript值對象數組

    JavaScript和Objective-C雖然都是面嚮對象語言,但其實現機制徹底不一樣,OC是基於類的,JS是基於原型的,而且他們的數據類型間也存在很大的差別。所以若要在Native和JS間無障礙的進行數據的傳遞,就須要一箇中間對象作橋接,這個對象就是JSValue。瀏覽器

4.JSExport網絡

    JSExport是一個協議,Native中遵照此解析的類能夠將方法和屬性轉換爲JS的接口供JS調用。

5.一些用於C語言的結構

    你必定注意到了,上圖的右下角還有一塊被虛線包圍的區域,其中的"類"都是C語言風格,JavaScriptCore框架是支持在Objective-C、Swift和C三種語言中使用的。

2、在Native中運行JavaScript腳本代碼

    咱們先來編寫一個最簡單的例子,使用OC代碼來執行一段JS腳本。首先新建一個文件,將其後綴設置爲.js,我這裏將它命令爲main.js,在其中編寫以下代碼:

(function(){
	console.log("Hello Native");
})();

上面是一個自執行的函數,其中打印了「Hello Native」字符串。在Native中編寫以下代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString * path = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"js"];
    NSData * jsData = [[NSData alloc]initWithContentsOfFile:path];
    NSString * jsCode = [[NSString alloc]initWithData:jsData encoding:NSUTF8StringEncoding];
    self.jsContext = [[JSContext alloc]init];
    [self.jsContext evaluateScript:jsCode];
}

須要注意,其實這裏我將建立的JSContext對象做爲了當前視圖控制器的屬性,這樣作的目的僅僅是爲了方便調試,不過不對此context對象進行引用,當viewDidLoad函數執行完成後,JS運行環境也將被銷燬,咱們就沒法在Safari中直觀的看到JS代碼的執行結果了。

    運行工程,記得要打開Safari瀏覽器的自動顯示JSContent檢查器,以下圖:

當iOS模擬器跑起來後,Safari會自動彈出開發者工具,在控制檯裏面能夠看到來自JavaScript的真摯問候:

剛纔咱們只是簡單了經過原生調用了一段JS代碼,可是若是Native在調JS方法時沒法傳參那也太low了,咱們能夠直接將要傳遞的參數格式化到字符串中,修改main.js文件以下:

function put(name){
	console.log("Hello "+name);
};
put(%@);

再封裝一個OC方法以下:

-(void)runJS_Hello:(NSString *)name{
    NSString * path = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"js"];
    NSData * jsData = [[NSData alloc]initWithContentsOfFile:path];
    NSString * jsCode = [[NSString alloc]initWithData:jsData encoding:NSUTF8StringEncoding];
    NSString * finiString = [NSString stringWithFormat:jsCode,name];
    [self.jsContext evaluateScript:finiString];
}

在viewDidLoad中進行調用,以下:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.jsContext = [[JSContext alloc]init];
    [self runJS_Hello:@"'阿凡達'"];
}

運行再看Safari控制檯的結果,編程了Hello 阿凡達~:

其實evaluateScript函數執行後會將JS代碼的執行結果進行返回,是JSValue類型的對象,後面會再介紹。

3、在JavaScript中調用Native方法

    有來無往非君子,一樣也能夠在原生中編寫方法讓JS來調用,示例以下:

- (void)viewDidLoad {
    [super viewDidLoad];
    void(^block)() = ^(){
        NSLog(@"Hello JavaScript");
    };
    self.jsContext = [[JSContext alloc]init];
    [self.jsContext setObject:block forKeyedSubscript:@"oc_hello"];
}

上面setObject:forKeyedSubscript:方法用來向JSContext環境的全局對象中添加屬性,這裏添加了一個函數屬性,取名爲oc_hello。這裏JavaScriptCore會自動幫咱們把一些數據類型進行轉換,會將OC的函數轉換爲JS的函數,運行工程,在Safari的控制檯中調用oc_hello函數,能夠看到在Xcode控制檯輸出了對JavaScript的真摯問候,以下:

一樣,若是聲明的block是帶參數的,JS在調用此OC方法時也須要傳入參數,若是block有返回值,則在JS中也能獲取到返回值,例如:

BOOL (^block)(NSString *) = ^(NSString *name){
        NSLog(@"%@", [NSString stringWithFormat:@"Hello %@",name]);
        return YES;
    };
    self.jsContext = [[JSContext alloc]init];
    [self.jsContext setObject:block forKeyedSubscript:@"oc_hello"];

4、深刻JSContext類

    看到這,你已經學會最基礎的OC與JS互相問好(交互)。下面咱們再來深刻看下JSContext中的屬性和方法。

    建立JSContext對象有以下兩種方式:

//建立一個新的JS運行環境
- (instancetype)init;
//建立一個新的JS運行環境 並關聯到某個虛擬機對象上
- (instancetype)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;

    執行JS代碼有以下兩個方法:

//執行JS代碼 結果將封裝成JSValue對象返回
- (JSValue *)evaluateScript:(NSString *)script;
//做用同上
- (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL *)sourceURL NS_AVAILABLE(10_10, 8_0);

    下面的屬性和方法能夠獲取到JS運行環境中的一些信息:

//當前的JS運行環境 當JS調用OC方法時,在OC方法中能夠用此方法獲取到JS運行環境
+ (JSContext *)currentContext;
//獲取當前執行的JS函數,當JS調用OC方法時,在OC方法中能夠用此方法獲取到執行的函數
+ (JSValue *)currentCallee;
//獲取當前執行的JS函數中的this指向的對象
+ (JSValue *)currentThis;
//獲取當前執行函數的參數列表,當JS調用OC方法時,在OC方法中能夠用此方法獲取到執行的函數的參數列表
+ (NSArray *)currentArguments;
//獲取當前JS運行環境的全局對象
@property (readonly, strong) JSValue *globalObject;
//當運行的JavaScript代碼拋出了未捕獲的異常時,這個屬性會被賦值爲拋出的異常
@property (strong) JSValue *exception;
//設置爲一個異常捕獲的block,若是異常被此block捕獲,exception屬性就再也不被賦值了
@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception);
//當前運行環境所關聯的虛擬機
@property (readonly, strong) JSVirtualMachine *virtualMachine;
//當前運行環境名稱
@property (copy) NSString *name;
//獲取當前JS運行環境全局對象上的某個屬性
- (JSValue *)objectForKeyedSubscript:(id)key;
//設置當前JS運行環境全局對象上的屬性
- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key;
//將C語言環境的JS運行環境轉換爲OC環境的JS運行環境
+ (JSContext *)contextWithJSGlobalContextRef:(JSGlobalContextRef)jsGlobalContextRef;
//C語言環境的JS運行上下文
@property (readonly) JSGlobalContextRef JSGlobalContextRef;

5、深刻JSValue類

    JSValue是JavaScript與Objective-C之間的數據橋樑。在Objective-C中調用JS腳本或者JS調用OC方法均可以使用JSValue來傳輸數據。其中屬性和方法示例以下:

//所對應的JS運行環境
@property (readonly, strong) JSContext *context;
//在指定的JS運行環境中建立一個JSValue對象
+ (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context;
//建立布爾值
+ (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context;
//建立浮點值
+ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context;
//建立32位整型值
+ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context;
//建立32位無符號整形值
+ (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context;
//建立空的JS對象
+ (JSValue *)valueWithNewObjectInContext:(JSContext *)context;
//建立空的JS數組
+ (JSValue *)valueWithNewArrayInContext:(JSContext *)context;
//建立JS正則對象
+ (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context;
//建立JS錯誤信息
+ (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context;
//建立JS null值
+ (JSValue *)valueWithNullInContext:(JSContext *)context;
//建立JS undefined值
+ (JSValue *)valueWithUndefinedInContext:(JSContext *)context;

JavaScript中的數據類型和Objective-C的數據類型仍是有着很大的差別,其中對應關係以下:

Objective-C  JavaScript
nil undefined
NSNull null
NSString string
NSNumber number boolean
NSDictionary    Object
NSArray Array
NSDate Date   
Block Function
id Object
Class Object

下面這些方法能夠將JSValue值轉換爲Objective-C中的數據類型:

//將JSValue轉換爲OC對象
- (id)toObject;
//將JSValue轉換成特定OC類的對象
- (id)toObjectOfClass:(Class)expectedClass;
//將JSValue轉換成布爾值
- (BOOL)toBool;
//將JSValue轉換成浮點值
- (double)toDouble;
//將JSValue轉換成32位整型值
- (int32_t)toInt32;
//將JSValue轉換成32位無符號整型值
- (uint32_t)toUInt32;
//將JSValue轉換成NSNumber值
- (NSNumber *)toNumber;
//將JSValue轉換成NSString值
- (NSString *)toString;
//將JSValue轉換成NSDate值
- (NSDate *)toDate;
//將JSValue轉換成NSArray值
- (NSArray *)toArray;
//將JSValue轉換成NSDictionary值
- (NSDictionary *)toDictionary;
//獲取JSValue對象中某個屬性的值
- (JSValue *)valueForProperty:(NSString *)property;
//設置JSValue對象中某個屬性的值
- (void)setValue:(id)value forProperty:(NSString *)property;
//刪除JSValue對象中的某個屬性
- (BOOL)deleteProperty:(NSString *)property;
//判斷JSValue對象中是否包含某個屬性
- (BOOL)hasProperty:(NSString *)property;
//定義JSValue中的某個屬性 這個方法和JavaScript中Object構造函數的defineProperty方法一致
/*
第2個參數設置此屬性的描述信息 能夠設置的鍵值以下:
NSString * const JSPropertyDescriptorWritableKey;//設置布爾值 是否可寫
NSString * const JSPropertyDescriptorEnumerableKey;//設置布爾值 是否可枚舉
NSString * const JSPropertyDescriptorConfigurableKey;//設置布爾值 是否可配置
NSString * const JSPropertyDescriptorValueKey;//設置此屬性的值
NSString * const JSPropertyDescriptorGetKey;//設置此屬性的get方法
NSString * const JSPropertyDescriptorSetKey;//設置此屬性的set方法
以上set、get方法的鍵和value、可寫性的鍵不能同時存在,其語法是JavaScript保持一致
*/
- (void)defineProperty:(NSString *)property descriptor:(id)descriptor;
//獲取JS數組對象某個下標的值
- (JSValue *)valueAtIndex:(NSUInteger)index;
//設置JS數組對象某個下標的值
- (void)setValue:(id)value atIndex:(NSUInteger)index;
//判斷此對象是否爲undefined
@property (readonly) BOOL isUndefined;
//判斷此對象是否爲null
@property (readonly) BOOL isNull;
//判斷此對象是否爲布爾值
@property (readonly) BOOL isBoolean;
//判斷此對象是否爲數值
@property (readonly) BOOL isNumber;
//判斷此對象是否爲字符串
@property (readonly) BOOL isString;
//判斷此對象是否爲object對象
@property (readonly) BOOL isObject;
//判斷此對象是否爲數組
@property (readonly) BOOL isArray;
//判斷此對象是否爲日期對象
@property (readonly) BOOL isDate;
//比較兩個JSValue是否全相等 對應JavaScript中的===
- (BOOL)isEqualToObject:(id)value;
//比較兩個JSValue對象的值是否相等 對應JavaScript中的==
- (BOOL)isEqualWithTypeCoercionToObject:(id)value;
//判斷某個對象是否在當前對象的原型鏈上
- (BOOL)isInstanceOf:(id)value;
//若是JSValue是Function對象 能夠調用此方法 和JavaScript中的call方法一致
- (JSValue *)callWithArguments:(NSArray *)arguments;
//若是JSValue是一個構造方法對象 能夠調用此方法 和JavaScript中使用new關鍵字一致
- (JSValue *)constructWithArguments:(NSArray *)arguments;
//用此對象進行函數的調用 當前對象會被綁定到this中
- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;
//將CGPoint轉換爲JSValue對象
+ (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context;
//將NSRange轉換爲JSValue對象
+ (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context;
//將CGRect轉換爲JSValue對象
+ (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context;
//將CGSize轉換爲JSValue對象
+ (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context;
//轉換成CGPoint數據
- (CGPoint)toPoint;
//轉換成NSRange數據
- (NSRange)toRange;
//轉換成CGRect數據
- (CGRect)toRect;
//轉換爲CGSize數據
- (CGSize)toSize;
//將C風格的JSValueRef對象轉換爲JSValue對象
+ (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context;

其實在JavaScriptCore框架中還有一個JSManagerValue類,這個的主要做用是管理內存。雖然咱們在編寫Objective-C代碼時有強大的自動引用技術(ARC技術),咱們通常無需關心對象的內存問題,在編寫JavaScript代碼時也有強大的垃圾回收機制(這種機制下甚至連循環引用都不是問題),可是在OC和JS混合開發時,就很容易出現問題了,好比一個JS垃圾回收機制釋放掉的對象OC中卻還在用,反過來也是同樣。JSManagerValue對JSValue進行了一層包裝,它能夠保證在適合時候使用這個對象時對象都不會被釋放,其中方法以下:

//建立JSVlaue對象的包裝JSManagerValue
+ (JSManagedValue *)managedValueWithValue:(JSValue *)value;
+ (JSManagedValue *)managedValueWithValue:(JSValue *)value andOwner:(id)owner;
- (instancetype)initWithValue:(JSValue *)value;
//獲取所包裝的JSValue對象
@property (readonly, strong) JSValue *value;

6、Objective-C與JavaScript複雜對象的映射

    咱們在使用JavaScript調用Objective-C方法的實質是將一個OC函數設置爲了JS全局對象的一個屬性,固然咱們也能夠設置非函數的屬性或者任意JSValue(或者能夠轉換爲JSValue)的值。例如:

self.jsContext = [[JSContext alloc]init];
    //向JS全局對象中添加一個獲取當前Native設備類型的屬性
    [self.jsContext setObject:@"iOS" forKeyedSubscript:@"deviceType"];

可是若是咱們想把OC自定義的一個類的對象設置爲JS全局對象的某個屬性,JS和OC有着徹底不一樣的對象原理,若是不作任何處理,JS是接收不到OC對象中定義的屬性和方法的。這時就須要使用到前面提到的JSExport協議,須要注意,這個協議不是用來被類遵照的,它裏面沒有規定任何方法,其是用來被繼承定義新的協議的,自定義的協議中約定的方法和屬性能夠在JS中被獲取到,示例以下:

OC中新建一個自定義的類:

@protocol MyObjectProtocol <JSExport>

@property(nonatomic,strong)NSString * name;
@property(nonatomic,strong)NSString * subject;
@property(nonatomic,assign)NSInteger age;
-(void)sayHi;


@end

@interface MyObject : NSObject<MyObjectProtocol>
@property(nonatomic,strong)NSString * name;
@property(nonatomic,strong)NSString * subject;
@property(nonatomic,assign)NSInteger age;
@end
@implementation MyObject
-(void)sayHi{
    NSLog(@"Hello JavaScript");
}
@end

添加到JS全局對象中:

MyObject* object = [MyObject new];
    object.name = @"Jaki";
    object.age = 25;
    object.subject = @"OC";
    [jsContext setObject:object forKeyedSubscript:@"deviceObject"];

在JS運行環境中能夠完整的到deviceObject對象,以下:

7、C語言風格的API解釋

    JavaScriptCore框架中除了包含完整的Objective-C和Swift語言的API外,也提供了對C語言的支持。

    與JS運行環境相關的方法以下:

//建立一個JSContextRef組
/*
JSContextRef至關於JSContext,同一組中的數據能夠共享
*/
JSContextGroupRef JSContextGroupCreate(void);
//內存引用
JSContextGroupRef JSContextGroupRetain(JSContextGroupRef group);
//內存引用釋放
void JSContextGroupRelease(JSContextGroupRef group);
//建立一個全局的運行環境
JSGlobalContextRef JSGlobalContextCreate(JSClassRef globalObjectClass);
//同上
JSGlobalContextRef JSGlobalContextCreateInGroup(JSContextGroupRef group, JSClassRef globalObjectClass);
//內存引用
JSGlobalContextRef JSGlobalContextRetain(JSGlobalContextRef ctx);
//內存引用釋放
void JSGlobalContextRelease(JSGlobalContextRef ctx);
//獲取全局對象
JSObjectRef JSContextGetGlobalObject(JSContextRef ctx);
//獲取JSContextRef組
JSContextGroupRef JSContextGetGroup(JSContextRef ctx);
//獲取全局的運行環境
JSGlobalContextRef JSContextGetGlobalContext(JSContextRef ctx);
//獲取運行環境名
JSStringRef JSGlobalContextCopyName(JSGlobalContextRef ctx);
//設置運行環境名
void JSGlobalContextSetName(JSGlobalContextRef ctx, JSStringRef name);

    與定義JS對象的相關方法以下:

//定義JS類
/*
參數JSClassDefinition是一個結構體 其中能夠定義許多回調
*/
JSClassRef JSClassCreate(const JSClassDefinition* definition);
//引用內存
JSClassRef JSClassRetain(JSClassRef jsClass);
//釋放內存
void JSClassRelease(JSClassRef jsClass);
//建立一個JS對象
JSObjectRef JSObjectMake(JSContextRef ctx, JSClassRef jsClass, void* data);
//定義JS函數
JSObjectRef JSObjectMakeFunctionWithCallback(JSContextRef ctx, JSStringRef name, JSObjectCallAsFunctionCallback callAsFunction);
//定義構造函數
JSObjectRef JSObjectMakeConstructor(JSContextRef ctx, JSClassRef jsClass, JSObjectCallAsConstructorCallback callAsConstructor);
//定義數組
JSObjectRef JSObjectMakeArray(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
//定義日期對象
JSObjectRef JSObjectMakeDate(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
//定義異常對象
JSObjectRef JSObjectMakeError(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
//定義正則對象
JSObjectRef JSObjectMakeRegExp(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
//定義函數對象
JSObjectRef JSObjectMakeFunction(JSContextRef ctx, JSStringRef name, unsigned parameterCount, const JSStringRef parameterNames[], JSStringRef body, JSStringRef sourceURL, int startingLineNumber, JSValueRef* exception);
//獲取對象的屬性
JSValueRef JSObjectGetPrototype(JSContextRef ctx, JSObjectRef object);
//設置對象的屬性
void JSObjectSetPrototype(JSContextRef ctx, JSObjectRef object, JSValueRef value);
//檢查對象是否包含某個屬性
bool JSObjectHasProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName);
//獲取對象屬性
JSValueRef JSObjectGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception);
//定義對象屬性
void JSObjectSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSPropertyAttributes attributes, JSValueRef* exception);
//刪除對象屬性
bool JSObjectDeleteProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception);
//獲取數組值
JSValueRef JSObjectGetPropertyAtIndex(JSContextRef ctx, JSObjectRef object, unsigned propertyIndex, JSValueRef* exception);
//設置數組值
void JSObjectSetPropertyAtIndex(JSContextRef ctx, JSObjectRef object, unsigned propertyIndex, JSValueRef value, JSValueRef* exception);
//獲取私有數據
void* JSObjectGetPrivate(JSObjectRef object);
//設置私有數據
bool JSObjectSetPrivate(JSObjectRef object, void* data);
//判斷是否爲函數
bool JSObjectIsFunction(JSContextRef ctx, JSObjectRef object);
//將對象做爲函數來調用
JSValueRef JSObjectCallAsFunction(JSContextRef ctx, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
//判斷是否爲構造函數
bool JSObjectIsConstructor(JSContextRef ctx, JSObjectRef object);
//將對象做爲構造函數來調用
JSObjectRef JSObjectCallAsConstructor(JSContextRef ctx, JSObjectRef object, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
//獲取全部屬性名
JSPropertyNameArrayRef JSObjectCopyPropertyNames(JSContextRef ctx, JSObjectRef object);
//進行內存引用
JSPropertyNameArrayRef JSPropertyNameArrayRetain(JSPropertyNameArrayRef array);
//內存釋放
void JSPropertyNameArrayRelease(JSPropertyNameArrayRef array);
//獲取屬性個數
size_t JSPropertyNameArrayGetCount(JSPropertyNameArrayRef array);
//在屬性名數組中取值
JSStringRef JSPropertyNameArrayGetNameAtIndex(JSPropertyNameArrayRef array, size_t index);
//添加屬性名
void JSPropertyNameAccumulatorAddName(JSPropertyNameAccumulatorRef accumulator, JSStringRef propertyName);

    JS數據類型相關定義在JSValueRef中,以下:

//獲取值的類型
/*
枚舉以下:
typedef enum {
    kJSTypeUndefined,
    kJSTypeNull,
    kJSTypeBoolean,
    kJSTypeNumber,
    kJSTypeString,
    kJSTypeObject
} JSType;
*/
JSType JSValueGetType(JSContextRef ctx, JSValueRef);
//判斷是否爲undefined類型
bool JSValueIsUndefined(JSContextRef ctx, JSValueRef value);
//判斷是否爲null類型
bool JSValueIsNull(JSContextRef ctx, JSValueRef value);
//判斷是否爲布爾類型
bool JSValueIsBoolean(JSContextRef ctx, JSValueRef value);
//判斷是否爲數值類型
bool JSValueIsNumber(JSContextRef ctx, JSValueRef value);
//判斷是否爲字符串類型
bool JSValueIsString(JSContextRef ctx, JSValueRef value);
//判斷是否爲對象類型
bool JSValueIsObject(JSContextRef ctx, JSValueRef value);
//是不是類
bool JSValueIsObjectOfClass(JSContextRef ctx, JSValueRef value, JSClassRef jsClass);
//是不是數組
bool JSValueIsArray(JSContextRef ctx, JSValueRef value);
//是不是日期
bool JSValueIsDate(JSContextRef ctx, JSValueRef value);
//比較值是否相等
bool JSValueIsEqual(JSContextRef ctx, JSValueRef a, JSValueRef b, JSValueRef* exception);
//比較值是否全等
bool JSValueIsStrictEqual(JSContextRef ctx, JSValueRef a, JSValueRef b);
//是不是某個類的實例
bool JSValueIsInstanceOfConstructor(JSContextRef ctx, JSValueRef value, JSObjectRef constructor, JSValueRef* exception);
//建立undefined值
JSValueRef JSValueMakeUndefined(JSContextRef ctx);
//建立null值
JSValueRef JSValueMakeNull(JSContextRef ctx);
//建立布爾值
JSValueRef JSValueMakeBoolean(JSContextRef ctx, bool boolean);
//建立數值
JSValueRef JSValueMakeNumber(JSContextRef ctx, double number);
//建立字符串值
JSValueRef JSValueMakeString(JSContextRef ctx, JSStringRef string);
//經過JSON建立對象
JSValueRef JSValueMakeFromJSONString(JSContextRef ctx, JSStringRef string);
//將對象轉爲JSON字符串
JSStringRef JSValueCreateJSONString(JSContextRef ctx, JSValueRef value, unsigned indent, JSValueRef* exception);
//進行布爾值轉換
bool JSValueToBoolean(JSContextRef ctx, JSValueRef value);
//進行數值轉換
double JSValueToNumber(JSContextRef ctx, JSValueRef value, JSValueRef* exception);
//字符串值賦值
JSStringRef JSValueToStringCopy(JSContextRef ctx, JSValueRef value, JSValueRef* exception);
//值與對象的轉換
JSObjectRef JSValueToObject(JSContextRef ctx, JSValueRef value, JSValueRef* exception);

    在C風格的API中,字符串也被包裝成了JSStringRef類型,其中方法以下:

//建立js字符串
JSStringRef JSStringCreateWithCharacters(const JSChar* chars, size_t numChars);
JSStringRef JSStringCreateWithUTF8CString(const char* string);
//內存引用於釋放
JSStringRef JSStringRetain(JSStringRef string);
void JSStringRelease(JSStringRef string);
//獲取字符串長度
size_t JSStringGetLength(JSStringRef string);
//轉成UTF8字符串
size_t JSStringGetUTF8CString(JSStringRef string, char* buffer, size_t bufferSize);
//字符串比較
bool JSStringIsEqual(JSStringRef a, JSStringRef b);
bool JSStringIsEqualToUTF8CString(JSStringRef a, const char* b);

8、Hybird App 構建思路

    Hybird App是指混合模式移動應用,即其中既包含原生的結構有內嵌有Web的組件。這種App不只性能和用戶體驗能夠達到和原生所差無幾的程度,更大的優點在於bug修復快,版本迭代無需發版。3月8日蘋果給許多開發者發送了一封警告郵件,主要是提示開發者下載腳本動態更改App本來行爲的作法將會被提審拒絕。其實此次郵件所提內容和Hybird App並沒有太大關係(對ReactNative也沒有影響),蘋果警告的是網絡下發腳本而且使用runtime動態修改Native行爲的應用,Hybird App的實質並無修改原Native的行爲,而是將下發的資源進行加載和界面渲染,相似WebView。

    關於混合開發,咱們有兩種模式:

    1.Native內嵌WebView,經過JS與OC交互實現業務無縫的銜接。

    不管是UIWebView仍是WKWebKit,咱們均可以在其中拿到當前的JSContext,然是使用前面介紹的方法即可以實現數據互通與交互。這種方式是最簡單的混合開發,但其性能和原生相比要差一些。示意圖以下:

    2.下發JS腳本,使用相似ReactNative的框架進行原生渲染

    這是一種效率很是高的混合開發模式,而且ReactNative也自己支持android和iOS公用一套代碼。咱們也可使用JavaScriptCore本身實現一套解析邏輯,使用JavaScript來編寫Native應用,要完整實現這樣一套東西太複雜了,咱們也沒有能力完成一個如此龐大的工程,可是咱們能夠作一個小Demo來模擬其原理,這樣能夠更好的幫助咱們理解Hybird App的構建原理。

咱們打算實現這樣的功能:經過下發JS腳本建立原生的UILabel標籤與UIButton控件,首先編寫JS代碼以下:

(function(){
    console.log("ProgectInit");
    //JS腳本加載完成後 自動render界面
    return render();
})();

//JS標籤類
function Label(rect,text,color){
    this.rect = rect;
    this.text = text;
    this.color = color;
    this.typeName = "Label";
}
//JS按鈕類
function Button(rect,text,callFunc){
    this.rect = rect;
    this.text = text;
    this.callFunc = callFunc;
    this.typeName = "Button";
}
//JS Rect類
function Rect(x,y,width,height){
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
}
//JS顏色類
function Color(r,g,b,a){
    this.r = r;
    this.g = g;
    this.b = b;
    this.a = a;
}
 //渲染方法 界面的渲染寫在這裏面
 function render(){
    var rect = new Rect(20,100,280,30);
    var color = new Color(1,0,0,1);
    var label = new Label(rect,"Hello World",color);
    var rect2 = new Rect(20,150,280,30);
    var color2 = new Color(0,1,0,1);
    var label2 = new Label(rect2,"Hello Native",color2);
    var rect3 = new Rect(20,200,280,30);
    var color3 = new Color(0,0,1,1);
    var label3 = new Label(rect3,"Hello JavaScript",color3);
    var rect4 = new Rect(20,240,280,30);
    var button = new Button(rect4,"我是一個按鈕",function(){
                            var randColor = new Color(Math.random(),Math.random(),Math.random(),1);
                            Globle.changeBackgroundColor(randColor);
                            });
    //將控件以數組形式返回
    return [label,label2,label3,button];
 }

建立一個Objective-C類綁定到JS全局對象上,做爲OC方法的橋接器:

//.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol GloblePrptocol <JSExport>
-(void)changeBackgroundColor:(JSValue *)value;
@end
@interface Globle : NSObject<GloblePrptocol>
@property(nonatomic,weak)UIViewController * ownerController;
@end
//.m
#import "Globle.h"

@implementation Globle
-(void)changeBackgroundColor:(JSValue *)value{
    self.ownerController.view.backgroundColor = [UIColor colorWithRed:value[@"r"].toDouble green:value[@"g"].toDouble blue:value[@"b"].toDouble alpha:value[@"a"].toDouble];
}
@end

在ViewController中實現一個界面渲染的render解釋方法,並創建按鈕的方法轉換,以下:

//
//  ViewController.m
//  JavaScriptCoreTest
//
//  Created by vip on 17/3/6.
//  Copyright © 2017年 jaki. All rights reserved.
//

#import "ViewController.h"
#import <JavaScriptCore/JavaScriptCore.h>
#import "Globle.h"
@interface ViewController ()

@property(nonatomic,strong)JSContext * jsContext;
@property(nonatomic,strong)NSMutableArray * actionArray;
@property(nonatomic,strong)Globle * globle;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //建立JS運行環境
    self.jsContext = [JSContext new];
    //綁定橋接器
    self.globle =  [Globle new];
    self.globle.ownerController = self;
    self.jsContext[@"Globle"] = self.globle;
    self.actionArray = [NSMutableArray array];
    [self render];
}
//界面渲染解釋器
-(void)render{
    NSString * path = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"js"];
    NSData * jsData = [[NSData alloc]initWithContentsOfFile:path];
    NSString * jsCode = [[NSString alloc]initWithData:jsData encoding:NSUTF8StringEncoding];
    JSValue * jsVlaue = [self.jsContext evaluateScript:jsCode];
    for (int i=0; i<jsVlaue.toArray.count; i++) {
        JSValue * subValue = [jsVlaue objectAtIndexedSubscript:i];
        if ([[subValue objectForKeyedSubscript:@"typeName"].toString isEqualToString:@"Label"]) {
            UILabel * label = [UILabel new];
            label.frame = CGRectMake(subValue[@"rect"][@"x"].toDouble, subValue[@"rect"][@"y"].toDouble, subValue[@"rect"][@"width"].toDouble, subValue[@"rect"][@"height"].toDouble);
            label.text = subValue[@"text"].toString;
            label.textColor = [UIColor colorWithRed:subValue[@"color"][@"r"].toDouble green:subValue[@"color"][@"g"].toDouble blue:subValue[@"color"][@"b"].toDouble alpha:subValue[@"color"][@"a"].toDouble];
            [self.view addSubview:label];
        }else if ([[subValue objectForKeyedSubscript:@"typeName"].toString isEqualToString:@"Button"]){
            UIButton * button = [UIButton buttonWithType:UIButtonTypeSystem];
            button.frame = CGRectMake(subValue[@"rect"][@"x"].toDouble, subValue[@"rect"][@"y"].toDouble, subValue[@"rect"][@"width"].toDouble, subValue[@"rect"][@"height"].toDouble);
            [button setTitle:subValue[@"text"].toString forState:UIControlStateNormal];
            button.tag = self.actionArray.count;
            [button addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
            [self.actionArray addObject:subValue[@"callFunc"]];
            [self.view addSubview:button];
            
        }
    }
}
//按鈕轉換方法
-(void)buttonAction:(UIButton *)btn{
    JSValue * action  = self.actionArray[btn.tag];
    //執行JS方法
    [action callWithArguments:nil];
}



@end

運行工程,效果以下圖所示,點擊按鈕便可實現簡單的界面顏色切換:

上面的示例工程我只實現了UILabel類與UIButton類的JS-OC轉換,若是將原生控件和JS對象再進行一層綁定,而且實現大部分JS類與原生類和他們內部的屬性,則咱們就開發了一套Hybird App開發框架,但並無這個必要,若是你對更多興趣,能夠深刻學習下ReactNative。

    文中的示例Demo我放在了Github上,地址以下:https://github.com/ZYHshao/Demo-Hybird

前端學習新人,有志同道合的朋友,歡迎交流與指導,QQ羣:541458536

相關文章
相關標籤/搜索