說到JavaScript腳本,iOS開發者都會想到一個名叫JavaScriptCore的框架。這個框架的確十分強大,其中封裝了一套JavaScript運行環境以及Native與JS數據類型之間的轉換橋樑。本篇博客主要討論如何使用此框架來在iOS應用中運行JavaScript腳本。javascript
在學習一個框架時,首先應該先了解整個框架的結構,拿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三種語言中使用的。
咱們先來編寫一個最簡單的例子,使用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類型的對象,後面會再介紹。
有來無往非君子,一樣也能夠在原生中編寫方法讓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"];
看到這,你已經學會最基礎的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;
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;
咱們在使用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對象,以下:
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);
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