美文网首页tomiOS-Web程序员
iOS 中的web开发(2)--- JavaScriptCore

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

作者: DevKyle | 来源:发表于2017-04-17 13:27 被阅读431次
    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

    相关文章

      网友评论

        本文标题:iOS 中的web开发(2)--- JavaScriptCore

        本文链接:https://www.haomeiwen.com/subject/fydtzttx.html