OC与JS交互(一) ~~~~~ JavaScriptCore

前言

  前段时间做app活动,发现与JS交互方面有些混乱,为了梳理交互操作,又重新看了一遍原生与JS交互方面的东西

  同时找到了下面参考文档中的深入浅出和全面解析写的还不错,多余的就不赘述了,自己总结一下,不想看的看官门可以跳过【个人总结】这一部分

个人总结

  • JavaScriptCore是iOS7.0+  macOS10.9+
  • JavaScriptCore中包括四个类:JSVirtualMachine、JSContext、JSManagedValue、JSValue
  • JSVirtualMachine实例表示JavaScript执行的独立环境,使用这个类有两个主要目的:支持并发JavaScript执行,以及管理在JavaScript和Objective-C或Swift之间桥接的对象的内存
  • JSVirtualMachine实例可以包括多个JSContext,JSContext之间可以传值(JSValue对象),JSVirtualMachine之间不能传值
  • 想要并发执行JavaScript,需要为每个线程创建单独的JSVirtualMachine实例
  • 使用JSManagedValue来管理JavaScript值,并将被管理值的所有权交给JavaScriptCore virtual machine
  • JSManagedValue是对JSValue的包装,加入了“有条件保留”的行为,从而达到自动管理的目的
    • JavaScript值可以通过JavaScript对象访问(既不受JavaScript回收机制影响)
    • JsManegedValue可以通过OC或Swift进行访问
  • 可以理解为JSManagedValue是对JSValue的弱引用

 交互操作

  

  首先要声明获取JSContext对象,有下面三个方式,三选一

  

- (void)webViewDidFinishLoad:(UIWebView *)webView
{

    // 1.这种方式需要传入一个JSVirtualMachine对象,如果传nil,会导致应用崩溃的。
    JSVirtualMachine *JSVM = [[JSVirtualMachine alloc] init];
    JSContext *JSCtx = [[JSContext alloc] initWithVirtualMachine:JSVM];

    // 2.这种方式,内部会自动创建一个JSVirtualMachine对象,可以通过        JSCtx.virtualMachine
    // 看其是否创建了一个JSVirtualMachine对象。
    JSContext *JSCtx = [[JSContext alloc] init];

    // 3. 通过webView的获取JSContext。
    JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

}

JSContext对象对应一个全局对象(global object)。例如web浏览器中的JSContext,起全局对象就是window对象。全局变量是全局对象的属性,可以通过JSValue对象或者context下标的方式来访问。

   所以感觉用方法3,去获取而不是创建更好一些。

 

  JavaScript 与 Objective-C 交互主要通过2种方式:

    block方式:使用block将响应方法暴露给JavaScript,从而完成Objective-C的一些操作

    JSExport 协议:通过继承JSExport协议的方式来导出指定的方法和属性,可以将OC的中某个对象直接暴露给JS使用,而且在JS中使用就像调用JS的对象一样自然。

 

 block方式

    OC变量 <---> JS变量

//执行的JS操作  为a赋值
JSValue *value = [context evaluateScript:@"var a = 1+2*3;"];

//获取a的值的三种方法
NSLog(@"a = %@", [context objectForKeyedSubscript:@"a"]);//通过context的实力方法objectForKeyedSubscript
NSLog(@"a = %@", [context.globalObject objectForKeyedSubscript:@"a"]);//通过context.globalObject的ObjectForKeyedSubscript实例方法
NSLog(@"a = %@", context[@"a"]); //通过下标

//输出结果Output: 
a = 7 
a = 7 
a = 7

 

  JS方法  --->  OC方法 

     JS方法

//点击调用shareClick方法
<input type="button" value="分享" onclick="shareClick()" />

//shareClick方法
function shareClick() {
    //调用原生share方法

    //调用方法不带参数
    share();

    //调用方法带参数
    share('测试分享的标题','测试分享的内容','url=http://www.baidu.com');
}

 

    OC方法

- (void)addShareWithContext:(JSContext *)context
{
    context[@"share"] = ^() {
        //从JavaScript代码传过来的参数
        NSArray *args = [JSContext currentArguments];
        //参数的一些处理
        if (args.count < 3) {
            return ;
        }
        NSString *title = [args[0] toString];
        NSString *content = [args[1] toString];
        NSString *url = [args[2] toString];

// 在这里执行分享的操作
}; }

 

  OC方法  --->  JS方法

     JS代码

function shareResult(channel_id,share_channel,share_url) {
    //拼接数据
    var content = channel_id+","+share_channel+","+share_url;
                asyncAlert(content);
    //修改当前returnValue的值
    document.getElementById("returnValue").value = content;
}

 

    OC代码 

- (void)addShareWithContext:(JSContext *)context
{
    context[@"share"] = ^() {
        NSArray *args = [JSContext currentArguments];
        
        if (args.count < 3) {
            return ;
        }
        NSString *title = [args[0] toString];
        NSString *content = [args[1] toString];
        NSString *url = [args[2] toString];
        // 在这里执行分享的操作
        
        // 将分享结果返回给js
        NSString *jsStr = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];
        [[JSContext currentContext] evaluateScript:jsStr];
    };
}

    

    这样的话也完成了JS的回调,既调用JS中的方法处理返回值

 

 JSExport 协议

简单的说就是写一个继承JSExport的协议,写一个类遵循该协议,将这个类作为一个变量交给JavaScriptCore。这样JavaScriptCore会自动认定继承JSExport这个类的协议为要导入到JavaScript的方法和属性列表,从而进行导入。

代码1显示了协议中的变量,方法和类方法。代码2显示了对应的JavaScript中调用的方法

    代码1

@protocol MyPointExports <JSExport>
//对于每个导出的Objective-C属性,JavaScriptCore都会在原型上创建JavaScript访问器属性。
@property double x;
@property double y;
//对于每个导出的实例方法,JavaScriptCore都会创建一个相应的JavaScript函数作为原型对象的属性。
- (NSString *)description;
- (instancetype)initWithX:(double)x y:(double)y;
//对于每个导出的类方法,JavaScriptCore在构造函数对象上创建一个JavaScript函数
+ (MyPoint *)makePointWithX:(double)x y:(double)y;
@end

    代码2

// Objective-C properties become fields.
point.x;
point.x = 10;
// Objective-C instance methods become functions.
point.description();
// Objective-C initializers can be called with constructor syntax.
var p = MyPoint(1, 2);
// Objective-C class methods become functions on the constructor object.
var q = MyPoint.makePointWithXY(0, 0);

    其中MyPoint为遵循MyPointExports的类

    

    你可能注意到了,后两个方法参数写法上似乎有些变化。    

    如上面英文提到的,初始化方法可以用构造函数方法调用

    而多参数问题,导入到JavaScript时由这么一个规则

    • 所有冒号都从方法选择器中删除
    • 任何后面冒号的小写字母都变成大写    

    例如:OC中为  doFoo:withBar:

       JavaScript中为  doFooWithBar 

 

    不好记? 苹果又贴心的给了一个宏来指定导出到Javascript的方法名 

@protocol MyClassJavaScriptMethods <JSExport>
JSExportAs(doFoo,
- (void)doFoo:(id)foo withBar:(id)bar
);
@end

 

    到此,用法方面就结束了

  

后记

其实如果你看了下面的参考文档,你就会觉得他们确实写的很全面,我再写这一篇的时候也这么觉得,同样也犹豫过,在很显然不如别人写的好的情况下要不要在写下去。

后来想清了一个问题,虽说不要重复造轮子,但是不会造轮子的话,永远只能当组装师搬运工,学会造轮子之后才能成为创造者。一千个读者就有一千个哈姆雷特,或许你的切入点、视角更好呢,就算不是完美,至少你走过了这一步,也印象更深刻了些。虽然不完美,但还是写完了,希望能够对你有所帮助。

PS:如果你看了官方文档,会发现,大多数信息源还是官方文档~~~

参考文档

  JavaScriptCore官方文档

  深入浅出 JavaScriptCore

  JavaScriptCore全面解析 (上篇)

  JavaScriptCore全面解析 (下篇)

  iOS UIWebView与JavaScript交互之JavaScriptCore

  iOS下JS与OC互相调用(四)--JavaScriptCore

  

  

 

 

 

  

posted @ 2018-05-16 10:23  崇祟  阅读(302)  评论(0编辑  收藏  举报