JavaScriptCore到解析 (下篇)

迎接大家关注腾讯云技术社区-博客园官方主页,我们拿持续以博客园也大家推荐技术精品文章哦~

殷源,专注移动客户端支付,微软Imagine
Cup中国区特等奖获得者,现就职于腾讯。

接JavaScriptCore到解析
(上篇)

六、 JSExport

JSExport商讨提供了同一种植声明式的法子去奔JavaScript代表码导出Objective-C的实例类及其实例方法,类措施及总体性。

1. 在JavaScript中调用native代码

些微种植办法:

  • Block

  • JSExport

Block的艺术要命简短,如下:

context[@"add"] = ^(NSInteger a, NSInteger b) {
    return a+b;
};

JSValue *resultValue = [context evaluateScript:@"add(5, 6)"];
//另外一种调用JS函数的方法
resultValue = [context[@"add"] callWithArguments:@[@(5), @(6)]];
NSLog(@"resultValue = %@", resultValue);

Output:

11

JSExport的法子要经连续JSExport商谈的主意来导出指定的章程和特性:

@class MyPoint;

@protocol MyPointExports <JSExport>
@property double x;
@property double y;
- (NSString *)description;
- (instancetype)initWithX:(double)x y:(double)y;
+ (MyPoint *)makePointWithX:(double)x y:(double)y;
@end

@interface MyPoint : NSObject <MyPointExports>
- (void)myPrivateMethod; // Not in the MyPointExports protocol, so
    not visible to JavaScript code.
+ (void)test;
@endltValue);

后续于JSExport协议的MyPointExports协议被的实例变量,实例方法和相近方式还见面给导出,而MyPoint类的-
(void)myPrivateMethod方法可不会见为导出。

以OC代码中我们这样导出:

//导出对象
context[@"point"] = [[MyPoint alloc] initWithX:6 y:8];

//导出类
context[@"MyPoint"] = [MyPoint class];

于JS代码中得这样调用:

// 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);

2. 导出OC方法和特性让JS

  • 默认情况下,一个Objective-C类的道与性是匪见面导出给JavaScript的。你不能不挑选指定的法子和总体性来导出。对于一个class实现之每个协议,如果是协议持续了JSExport协议,JavaScriptCore就拿这协议的办法与属性列表导出给JavaScript。

  • 于各一个导出的实例方法,JavaScriptCore都见面当prototype中创造一个存取器属性。对于各一个导出的好像方式,JavaScriptCore会在constructor对象被创造一个对应之JavaScript
    function。

  • 在Objective-C中通过@property扬言的性质决定了JavaScript中的照应属性之表征:

ECMAScript 1

  • Objective-C类吃之习性,成员变量和返回值都以依据JSValue指定的正片协议进行转移。

3. 函往往名为转移

易成驼峰形式:

  • 破具有的冒号

  • 不无冒号后底第一独小写字母都见面为转为大写

ECMAScript 2

4. 打定义导出函数叫做

若非爱好默认的转换规则,也得运用JSExportAs来自定义转换

ECMAScript 3

5. 导出OC对象给JS

  • 何以导出自定义的靶子?

  • 自打定义对象来复杂的后续关系是怎么样导出的?

于座谈这个话题之前,我们先是得对JavaScript中之对象和持续关系有所了解。

七、 JavaScript对象继承

如您就了解JavaScript的目标继承,可以跨了本节。

此地见面快速介绍JavaScript对象继承的片知识:

1. JavaScript之数据类型

新式的 ECMAScript 标准定义了 7 种植多少列:

6 栽 原始类型:

  • Boolean

  • Null

  • Undefined

  • Number

  • String

  • Symbol (ECMAScript 6 新定义)和 Object

2. JavaScript原始值

除了 Object
以外的有着品种且是不可变的(值我无法让转)。我们遂这些项目的值吗“原始值”。

  • 布尔品种:两单价:true 和 false

  • Null 类型:只有一个价: null

  • Undefined 类型:一个不曾为赋值的变量会有个默认值 undefined

  • 数字型

  • 字符串类型:不同让类 C 语言,JavaScript
    字符串是不可改变的。这意味着字符串一旦让创造,就未可知为修改

  • 号类型

3. JavaScript对象

以 Javascript
里,对象足以吃看做是如出一辙组属性之汇聚。这些性还可让增减。属性之值好是不管三七二十一档次,包括持有复杂性数据结构的对象。

以下代码构造了一个point对象:

var point = { 
    x : 99, 
    y : 66, 
    revers : function() { 
        var tmp = this.x 
        this.x = this.y 
        this.y = tmp 
    }, 
    name : 'BiuBiuBiu', 
    next : null 
} 

point.revers();

4. JavaScript属性

ECMAScript定义的对象被来零星栽特性:数据性和访问器属性。

  • 多少性

数量性是键值对,并且每个数据性拥有下列特征:

ECMAScript 4

  • 顾器属性

做客器属性有一个要零星独访问器函数 (get 和 set)
来存取数价,并且有以下特征:

ECMAScript 5

5. JavaScript属性设置与检测

  • 安装一个靶的性质会单独会窜要新长其自出总体性,不见面改该后续的同名属性

  • 调用一个靶的性质会挨个检索自己及其继承的属性,直到检测及

var point = {x:99, y:66}; 
var childPoint = Object.create(point); 
console.log(childPoint.x) 
childPoint.x = 88 
console.log(childPoint.x)

Output:

99
88

于chrome的控制台中,我们独家打印设置x属性前后point对象的内部结构:

设置前

ECMAScript 6

设置后

ECMAScript 7!

看得出,设置一个目标的性并无见面修改该持续的特性,只会改或增其由来性能。

此地我们提到了proto和连续属性,下面我们详细讲解。

八、 Prototype

JavaScript对于发出因类的言语经验的开发人员来说稍令人困惑 (如Java或C
++)
,因为她是动态的,并且我不提供类似实现。(在ES2015/ES6遭遇引入了class关键字,但是只有是语法糖,JavaScript
仍然是因原型的)。

当提到后续时,Javascript
只出同一种结构:对象。每个对象还出一个里头链接到另外一个对象,称为它的原型
prototype。该原型对象来谈得来的原型,等等,直到上一个盖null为原型的对象。根据定义,null没有原型,并且作为这原型链
prototype chain中之结尾链接。

任何一个对象还产生一个proto属性,用来表示该继续了哟原型。

以下代码定一个备继往开来关系的靶子,point对象继承了一个具有x,y属性的原型对象。

var point = { 
    name : null, 
    __proto__ : { 
        x:99, 
        y:66, 
        __proto:Object.prototype 
    } 
}

Object.prototype.__proto__ == null        \\true

以Chrome的控制台中,我们打印对象组织:

ECMAScript 8

足见继承关系,point继承的原型又连续了Object.prototype,而Object.prototype的proto指向null,因而其是延续关系之极限。
这边我们首先使知prototype和proto是零星栽特性,前者只生function才有,后者有着的目标还发。后面会详细讲到。

1. JavaScript类?

Javascript 只发同一种结构:对象。类的概念而从何而来?

当JavaScript中我们可由此function来模拟类,例如我们定义一个MyPoint的函数,并把他服作MyPoint类,就足以经new来创造具有x,y属性的靶子

function MyPoint(x, y) { 
    this.x = x; 
    this.y = y; 
} 

var point = new MyPoint(99, 66);

打印point对象组织:

ECMAScript 9

此地出现一个constructor的定义

2. JavaScript constructor

每个JavaScript函数都自动拥有一个prototype的性能,这个prototype属性是一个靶,这个目标涵盖唯一一个不可枚举属性constructor。constructor属性值是一个函数对象

履以下代码我们会发现于任意函数F.prototype.constructor == F

var F = function(){}; //一个函数对象F 

var p = F.prototype; //F关联的原型对象 

var c = p.constructor; //原型对象关联的constructor函数

c == F // =>true: 对于任意函数F.prototype.constructor == F

此间就是存在一个反向引用的涉及:

ECMAScript 10

3. new发生了什么?

当调用new MyPoint(99,
66)时,虚拟机生成了一个point对象,并调用了MyPoint的prototype的constructor对象对point进行初始化,并且自动将MyPoint.prototype作为新对象point的原型。
一定给下的伪代码

var point ;
point = MyPoint.prototype.constructor(99,66);
point.__proto__ = MyPoint.prototype;

ECMAScript 11

4. _ proto __ 与prototype

概括地游说:

  • _proto__凡是所有目标的属性,表示对象好连续了什么目标

  • prototype是Function的属性,决定了new出来的新对象的proto

而图详细分解了两者的区分

ECMAScript 12!

5. 打印JavaScript对象组织

  • 每当浏览器提供的JavaScript调试工具被,我们好生便利地打印出JavaScript对象的内部结构

  • 于Mac/iOS客户端JavaScriptCore中并不曾如此的打印函数,这里我起定义了一个打印函数。鉴于对象的内部结构容易出现循环引用导致迭代打印陷入死循环,我们以此间大概地拍卖,对性不开展迭代打印。为了描述对象的原型链,这里手动在对象末尾对其原型进行打印。

function __typeof__(objClass)
{
    if ( objClass && objClass.constructor )
    {
        var strFun = objClass.constructor.toString();
        var className = strFun.substr(0, strFun.indexOf('('));
        className = className.replace('function', '');
        return className.replace(/(^\s*)|(\s*$)/ig, '');
    }
    return typeof(objClass);
}

function dumpObj(obj, depth) {

    if (depth == null || depth == undefined) {
        depth = 1;
    }
    if (typeof obj != "function" && typeof obj != "object") {
        return '('+__typeof__(obj)+')' + obj.toString();
    }

    var tab = '    ';
    var tabs = '';
    for (var i = 0; i<depth-1; i++) {
        tabs+=tab;
    }

    var output = '('+__typeof__(obj)+') {\n';

    var names = Object.getOwnPropertyNames(obj);
    for (index in names) {
        var propertyName = names[index];

        try {
            var property = obj[propertyName];
            output += (tabs+tab+propertyName + ' = ' + '('+__typeof__(property)+')' +property.toString()+ '\n');
        }catch(err) {
            output += (tabs+tab+propertyName + ' = ' + '('+__typeof__(property)+')' + '\n');
        }
    }

    var prt = obj.__proto__;
    if (typeof obj == "function") {
        prt = obj.prototype;
    }

    if (prt!=null && prt!= undefined) {
        output += (tabs+tab+'proto = ' + dumpObj(prt, depth+1) + '\n');
    }else {
        output += (tabs+tab+'proto = '+prt+' \n');
    }

    output+=(tabs+'}');
    return output;
}

function printObj(obj) {
    log(dumpObj(obj));
}

6. log

咱俩也富有的context都长一个log函数,方便我们当JS中于决定高出口日志

context[@"log"] = ^(NSString *log) {
        NSLog(@"%@", log);
};

九、 导出OC对象给JS

现今咱们累回来Objective-C中,看下OC对象是怎导出的

1. 粗略对象的导出

当您于一个不指定拷贝协议的Objective-C实例创建一个JavaScript对象时,JavaScriptCore会创建一个JavaScript的wrapper对象。对于具体项目,JavaScriptCore会自动拷贝值到适当的JavaScript类型。

以下代码定义了一个后续自NSObject的简短近乎

@interface DPoint : NSObject

@property (nonatomic, retain) NSString *type;

@end

导出对象

DPoint *dPoint = [[DPoint alloc] init];
dPoint.type = @"Hello Point!";
//导出对象
context[@"d_point"] = dPoint;
[context evaluateScript:@"printObj(d_point)"];

然后我们打印JavaScript中之d_point对象组织如下:

//Output
() { 
    proto = () { 
        constructor = (Object)[object DPointConstructor] 
        proto = (Object) { 
            toString = (Function)function toString() { [native code] } 
            toLocaleString = (Function)function toLocaleString() { [native code] } 
            valueOf = (Function)function valueOf() { [native code] } 
            hasOwnProperty = (Function)function hasOwnProperty() { [native code] } 
            propertyIsEnumerable = (Function)function propertyIsEnumerable() { [native code] } 
            isPrototypeOf = (Function)function isPrototypeOf() { [native code] } 
            __defineGetter__ = (Function)function __defineGetter__() { [native code] } 
            __defineSetter__ = (Function)function __defineSetter__() { [native code] } 
            __lookupGetter__ = (Function)function __lookupGetter__() { [native code] } 
            __lookupSetter__ = (Function)function __lookupSetter__() { [native code] } 
            __proto__ = (object) 
            constructor = (Function)function Object() { [native code] } 
            proto = null 
        } 
    } 
}

可见,其type属性并没被导出。

JS中之对象原型是就是Object.prototype。

2. 继往开来关系之导出

当JavaScript中,继承关系是由此原型链(prototype
chain)来支撑之。对于每一个导出的Objective-C类,JavaScriptCore会在context中创造一个prototype。对于NSObject类,其prototype对象就是是JavaScript
context的Object.prototype。

对此持有其他的Objective-C类,JavaScriptCore会创建一个prototype属性指于该父类的原型属性之原型对象。如此,JavaScript中之wrapper对象的原型链就体现了Objective-C中列的继续关系。

我们让DPoint继承子MyPoint

@interface DPoint : MyPoint

@property (nonatomic, retain) NSString *type;

@end

当OC中,它的继承关系是如此的

ECMAScript 13

当JS中,它的存续关系是这般的

ECMAScript 14

打印对象组织来证实:

//导出类
context[@“DPoint"] = [DPoint class] ;
[context evaluateScript:@“log(Dpoint.prototype.constructor==DPoint)"];
[context evaluateScript:@"printObj(DPoint)"];

Output:

true
(Function) { 
    name = (String)DPoint 
    prototype = (DPoint)[object DPointPrototype] 
    proto = (DPoint) { 
        constructor = (Function)function DPoint() { [native code] } 
        proto = (MyPoint) { 
            constructor = (Function)function MyPoint() { [native code] } 
            description = (Function)function () { [native code] } 
            x = (Function) 
            y = (Function) 
            proto = (Object) {
        toString = (Function)function toString() { [native code] } 
        toLocaleString = (Function)function toLocaleString() { [native code] } 
        ……        
        __proto__ = (object) 
        constructor = (Function)function Object() { [native code] } 
        proto = null 
    } 
        } 
    } 
}

足见,DPoint自身的未导出底性type没有在JS对象被反响出,其持续的MyPoint的导出的性和函数都以JS对象的原型中。

十、 内存管理

1. 循环引用

事先已经讲到,
每个JSValue对象都操有其JSContext对象的胜引用,只要有另外一个和一定JSContext关联的JSValue被所有(retain),这个JSContext就会见一直满怀活。如果我们拿一个native对象导出给JavaScript,即将这个目标及由JavaScript的大局对象有
,引用关系是如此的:

ECMAScript 15

这儿要我们当native对象中高引用持有JSContext或者JSValue,便会导致循环引用:

ECMAScript 16

之所以于使时只要顾以下几点:

2. 幸免直接采用外部context

  • 避在导出的block/native函数中直接以JSContext

  • 使用 [JSContext currentContext] 来获取当前context能够避免循环引用

//错误用法
context[@"block"] = ^() {
    NSLog(@"%@", context);
};

//纠正用法
context[@"block"] = ^() {
    NSLog(@"%@", [JSContext currentContext]);
};

3. 避免直接行使外部JSValue

  • 免以导出的block/native函数中一直下JSValue

//错误用法
JSValue *value = [JSValue valueWithObject:@"test“ inContext:context];
context[@"block"] = ^(){
    NSLog(@"%@", value);
};

//纠正用法
JSValue *value = [JSValue valueWithObject:@"test“ inContext:context];
JSManagedValue *managedValue = [JSManagedValue managedValueWithValue:value     andOwner:self];
context[@"block"] = ^(){
    NSLog(@"%@", [managedValue value]);
};

这边我们利用了JSManagedValue来解决之题目

十一、 JSManagedValue

  • 一个JSManagedValue对象涵盖了一个JSValue对象,“有标准化地有着(conditional
    retain)”的特色使其好自动管理内存。

  • 极端中心的用法就是因此来在导入到JavaScript的native对象吃储存JSValue。

  • 不用当以一个导出到JavaScript的native对象中兼有JSValue对象。因为每个JSValue对象都富含了一个JSContext对象,这种干将会招致循环引用,因而可能引致内存泄漏。

1. 有ECMAScript格地具有

所谓“有原则地拥有(conditional
retain)”,是凭借以以下简单栽情况其他一个饱的动静下保证其管理的JSValue被抱有:可以经JavaScript的目标图找到该JSValue

  • 可透过native对象图找到该JSManagedValue。使用addManagedReference:withOwner:方法可为虚拟机记录该干反之,如果上述条件且无饱,JSManagedValue对象就是会见以那value置为nil并释放该JSValue。

  • JSManagedValue对其涵盖的JSValue的具有关系与ARC下的虚引用(weak
    reference)类似。

2. 为何未直接用虚引用?

寻常咱们下weak来修饰block内得采取的外部引用以避免循环引用,由于JSValue对应之JS对象内存由虚拟机进行管制并当回收,这种方法无克确切地控制block内之援JSValue的生命周期,可能于block内亟待采取JSValue的下,其曾经于虚拟机回收。

API Reference

/* 可以直接使用JSManagedValue的类方法直接生产一个带owner的对象 */
+ managedValueWithValue:andOwner:

/* 也可以使用JSVirtualMachine的实例方法来手动管理 */
addManagedReference:withOwner: 
removeManagedReference:withOwner:


/* owner即JSValue在native代码中依托的对象,虚拟机就是通过owner来确认native中的对象图关系 */

十二、 异常处理

  • JSContext的exceptionHandler属性可用来接收JavaScript中丢掉来之那个

  • 默认的exceptionHandler会将exception设置给context的exception属性

  • 因而,默认的表现即是自JavaScript中丢掉给native的无处理的生而给废回到JavaScript中,异常没有受擒获处理。

  • 以context.exception设置也nil将会造成JavaScript认为很都被抓获处理。

@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception);

context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
    NSLog(@"exception : %@", exception);
    context.exception = exception;
};

参考:

https://trac.webkit.org/wiki/JavaScriptCore

https://trac.webkit.org/browser/trunk/Source/JavaScriptCore

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype

https://developer.apple.com/reference/javascriptcore

iOS7新JavaScriptCore框架入门介绍

JavaScriptCore框架在iOS7中的对象交互和管理

 

连锁推荐

玩转JavaScript正则表达式
前端 fetch
通信
构建流式应用—RxJS详解


 

此文已由作者授权腾讯云技术社区发布,转载请注明章出处
初稿链接:https://www.qcloud.com/community/article/516026
获得更多腾讯海量技术实施干货,欢迎大家去腾讯云技术社区