iOS 中的web开发(2)--- JavaScriptCore

JavaScriptCore-feature.png

JavaScriptCore其实也是一个很老的API了,之前都是在mac应用中使用,是纯c的代码,在iOS7中,用Objective-C进行了封装。JavaScriptCore允许我们在不使用UIWebViewWKWebView的情况下去执行JavaScript代码。而JavaScript作为web开发的一门脚本语言,在跨平台的应用上,有着很重要的作用。许多跨平台方案,比如JSPatch,React-Native都是基于JavaScriptCore作为iOS 端的实现方式。

JavaScriptCore

首先我们需要认识一下JavaScriptCore这个库里有什么东西。其实这个头文件只是导入了5个头文件

#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"

对于这个五个类,简单总结如下

  1. JSContext是JavaScript的运行上下文,是执行JavaScript代码和注册native方法接口的执行环境。
  2. JSValueJSContext执行后的返回结果,可以是任何JavaScript类型,JSValue提供很多的方法进行JavaScript和Objective-C类型的转换。但是需要注意的是JSValue是一个强引用类型,JavaScript内部通过垃圾回收机制进行内存管理。JavaScript和Objective-C类型的转换可以参考JSValue提供的方法。
  3. JSManagedValueJSValue的封装,用它可以解决JavaScript和native代码之间循环引用的问题。
  4. JSVirtualMachine管理JavaScript运行时和管理JavaScript暴露的native对象的内存。一般情况下是不需要直接和这个类交流的。但是如果你需要并行执行JavaScript代码,JSContext就需要运行在不同的JSVirtualMachine之上。每一个JSVirtualMachine有着自己的
  5. JSExport是一个协议,通过实现它可以完成把一个native对象暴漏给JavaScript。

引用一张图来说明上述类型的一些关系


javascriptcore-700x310.png

Objective-C 使用JavaScript

JavaScript没有类的概念,可以通过原型继承的方式实现继承。Objective-C可以调用JavaScript的函数和JavaScript属性。这些函数和属性需要先以NSString的形式被加载到JSContext环境上去,执行成功后,如果有返回值的话,返回一个JSValue对象。一般情况下,JavaScript文件都会存在本地,打包进你的程序中去,当然也可以从网络中获取JavaScript文件,加载到context中去,这就是我们讲的热跟新。

函数和属性

现有如下JavaScript代码

//jscore.js
var jsGlobleVar = "JS Demo";
function min(a,b){
    return a-b;
};

那么这个min方法就可以在Objective-C中以类似key-value的形式被检索到,这个value就是一个JSValue,然后调用JSValue实例的callWithArguments传入Objective-C的类型的参数,在JavaScript环境中会进行相应的类型转换

//JSCoreViewController.m
  NSString *jsCode = [self readFile]; // jscore.js
    [self.context evaluateScript:jsCode]; //将上述min函数加载到context环境中去 
    JSValue *min = [self.context[@"min"] callWithArguments:@[@2,@4]]; // self.context[@"min"] 对应的就是js中的 min函数或者min属性
    JSValue *jsVar = self.context[@"jsGlobleVar"];
     NSLog(@"jsGlobleVar-----%@",[jsVar toString]);// "JS Demo"
    NSLog(@"min+++++%d",[min toInt32]); //-2

JavaScript 调用 Objective-C

JavaScript调用Objective-C的block

如下代码,向context注册了key 为multi的 JSValue对象

  self.context[@"multi"]  = ^(NSInteger a,NSInteger b){
        return a*b;
    };

相当在JavaScript中定义一个如下的函数

        function multi (a,b){
             return a*b;
        }

那么在context调用这个block的方式和Objective-C中调用JavaScript的形式是一致的即

  JSValue *multi  = [self.context[@"multi"] callWithArguments:@[@3,@4]];
    //    或者
    multi = [self.context evaluateScript:@"multi(10,2)"];

自定义类型和方法

除了使用JSContext下标方法暴露JavaScript对象以外,还可以使用JSExprot协议把Objective-C中的自定义类转换为JSValue,并暴露给JavaScript对象,在JavaScript的环境中操作这个Objective-C对象。
首先需要定义个遵循JSExport的子协议。在子协议中规定了哪些属性和方法可以在JavaScript环境中可用。由于JavaScript中函数参数没有类型,所以所有的Objective-C方法都会以驼峰命名的方式被调用,比如-(void)minuse:(NSInteger)a b:(NSInteger)b c:(NSInteger)c;将以minuseBC(a,b,c)的方式被调用。也可以通过JSExportAs宏重自定义在JavaScript中被调用的方法名//JSExportAs(add, - (NSInteger)add:(NSInteger)a b:(NSInteger)b)。总结一下JSExport的主要功能有

  1. 将Objective-C的函数和属性传给JavaScript
  2. @property---> JavaScript的getter/setter
  3. Objective-C 实例方法 ---> JavaScript 函数Objective-C
  4. Objective-C类方法----> 在全局类对象上的JavaScript函数

Objective-C对象

先定义如下的Objective-C协议,并写个类遵循这个协议(这里使用JSExportModel来定义)

@protocol JSModelProtocol <JSExport>
-(void)minuse:(NSInteger)a b:(NSInteger)b c:(NSInteger)c;
@property (assign,nonatomic)NSInteger sum;
@end

接下来就可以将遵循了这个协议的类JSExportModel的实例对象加到执行环境中去了,并调用在JavaScript中已经被加载到context中的useOCObject方法。

-(void)excuteOCCdoeInJS{
    JSExportModel *model = [JSExportModel new];
    self.context[@"model"] = model;
    [self.context[@"useOCObject"] callWithArguments:nil];
    NSLog(@"%ld",(long)model.intV);//14
}

useOCObject的JavaScript代码如下

function useOCObject(){
    model.minuseBC(100,12,12);
    model.intV = 14;
};

这里调用了Objective-C的对象的方法,并对传入的对象的属性进行了修改。

传入一个类对象

因为JavaScript没有类的概念,所有传入到JavaScript环境中的Objective-C类就变为了一个Constructor对象,如果你需要在JavaScript环境中生成一个对象,你需要使这个Objective-C类有个类方法来生成实例对象,即在之前的协议中,我们先定义一个类方法用来生成实例对象

//在协议中声明,来暴露给JavaScript
+(instancetype)createWithIntV:(NSInteger)value;

然后在Objective-C中进行如下调用

-(void)excuteOCClassInJS{
    self.context[@"Model"] = [JSExportModel class];
   JSValue *returned = [self.context[@"useOCClass"] callWithArguments:nil];
   JSExportModel *m= [returned toObjectOfClass:[JSExportModel class]];
    NSLog(@"%ld",(long)m.intV); //12
  
}

JavaScript代码如下所示

function useOCClass(){
    var m =  Model.createWithIntV(12);
    m.minuseBC(10,1,1); // 调用Objective-C方法
    return m
}

可以看到我们,上述Objective-C代码打印输出了12。

返回一个JavaScript函数

在JavaScript中函数也是变量,是一等公民,也可以被返回。在JavaScript文件中有个JavaScriptFunc函数,这个函数如下所示

function callback (){
    // 这里打印的东西可以在 safari浏览器上的开发选项中打开
console.log("method----");
};
function jsFunc(){
    Obj.jsValue = callback // 直接对变量赋值
    return callback;  //将function 以callback的形式返回
}

这个函数就是将callback函数返回给Objective-C对象。上述第一种是将
函数对象直接赋值给Objective-C对象,第二种直接返回函数对象。可以在Objective-C中用如下方式调用函数。

-(void)jsReturnBlock{
     self.obj.jsValue = [self.context[@"jsFunc"] callWithArguments:nil];
    [ self.obj.jsValue callWithArguments:nil];
    
    self.context[@"Obj"] = self.obj;
    [self.context[@"jsFunc"] callWithArguments:nil];
    [ self.obj.jsValue callWithArguments:nil];
}

内存管理

我们都知道Objective-C使用ARC进行内存管理,JavaScriptCore(virtualMechine)内部通过垃圾回收机制来进行内存管理,所有的引用都是强引用。JavaScriptCore内部则保证了大多数的内存管理都是自动进行的,不需要我们进行额外的内存管理。但是还是得注意两种情况下的内存管理

  1. 在Objective-C对象中存储JavaScript的值
  2. 将JavaScript区域(主要是函数)加到Objective-C对象上中去
self.jsValue = [JSValue new];
    self.context[@"block"] = ^(){
        JSValue *value = self.jsValue;
        NSLog(@"%@",value);
    };

这里和普通block对象造成循环引用的类似,self.context 引用self,而本身self保有context。这种情况下,其实编译器也会告诉你这里产生了循环引用。 最好的方法就是将这个jsValue作为block的参数传到JavaScript环境中去。

还有一种情况就是在block中需要使用JS中其他的对象或者函数,需要从当前的JSContext获取,这时候就造成了循环引用,推荐的方式则是通过+[JSContext currentContext]来获取当前的context,即

self.jsValue = [JSValue new];
    self.context[@"block"] = ^(){
     // 循环引用
     // context = self.context;
        JSContext *context = [JSContext currentContext];
        NSLog(@"%@",value);
    };

还有一种情况是你必须要用一个Objective-C对象去保存一个JavaScript的值或者函数,你必须使用JSManagedValue去弱引用JavaScript对象。JSManagedValue本身就是一个弱引用JavaScript对象的对象。-addManagedReference:withOwner:JSManagedValue对象加入到了垃圾回收的引用。这个函数的意思就是JavaScript的垃圾回收机制观察引用Objective-C对象ower,如果存在,那么它就不回收这个对象,否则就回收这个对象。

线程

JSVirtualMachine保证了JavaScript代码执行的线程安全,锁都是JSVirtualMachine来控制。使用不同的JSVirtualMachine来实现串行或并发。

JavaScriptCore 和UIWebView

UIWebView中能够调用JavaScript代码,其本质上还是UIWebView将JavaScript代码在其内部的JSContext上执行。可以在UIWebView加载完后调用,获取到这个_context。

-(void)webViewDidFinishLoad:(UIWebView *)webView{
     _context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    [_context evaluateScript:@"showWebViewAlert()"];
    
}

网上有说documentView.webView.mainFrame.javaScriptContext是私有API,会有被拒的风险,需谨慎。

JavaScriptCore 和WKWebView

WKWebViewJavaScriptCore之间的通信,可以通过WKWebView的代理实现。在js代码中 通过发送如下 消息
window.webkit.messageHandlers.AppModel.postMessage({body: 'call js alert in js'});
WKWebView只需要在启动的时候绑定了AppModel就可以接收到JS发过来的消息了。具体如下

WKUserContentController *contentVC = [WKUserContentController new];
    //然后就可以通过代理取到js发来的消息了
    [contentVC addScriptMessageHandler:self name:@"AppModel"];
    config.userContentController = contentVC;

这样就可以WKScriptMessageHandler受到消息了

// MARK: - WKScriptMessageHandler
-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    // 收到消息
     // window.webkit.messageHandlers.AppModel.postMessage({body: 'call js alert in js'});
    NSLog(@"%@",message.body);
}

demo

demo下载地址iOS 中使用JS的demo ,喜欢的就star一下吧。。哈哈

参考资料

Java​Script​Core
JavaScriptCore by Example
JavaScriptCore and iOS 7
JavaScriptCore Tutorial for iOS: Getting Started
Integrating JavaScript into Native Apps

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容