说到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