查看: 2793|回复: 0

深入了解Swift

[复制链接]
  • TA的每日心情
    开心
    2014-6-16 10:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    40

    主题

    43

    帖子

    278

    积分

    中级会员

    Rank: 3Rank: 3

    积分
    278
    发表于 2015-1-13 00:00:56 | 显示全部楼层 |阅读模式

    本文由翻译小组成员 昨夜城 (微博)翻译,dada(github主页)校对。欢迎加入我们的译者小组(support@cocoachina.com,并注明社区ID、工作状态、电话以及个人博客等任何让我们更了解你的方式)


    原文:Inside Swift


    Swift是苹果新的编程语言,很多人都认为它可以“替换”Objective-C,但事实却并非如此。我花了一些时间对Swift运行时和二进制文件进行了逆向工程,不过(不过表转折,可去掉)发现了很多有趣的事。总结起来就是:Swift是没有消息机制的Objective-C。


    面向对象

    不管你信不信,Swift对象实际上是Objective-C对象。在Mach-O二进制文件中可以发现__objc_classlist部分包含了每个类的二进制数据,结构类似:


    struct objc_class {

    uint64_t isa;

    uint64_t superclass;

    uint64_t cache;

    uint64_t vtable;

    uint64_t data;

    };(注:所有的结构都是在64位机器上编译)


    注意数据入口,它指向一个列出了类的方法、实例变量以及协议等等的结构。通常情况下,数据是8个字节对齐的。然而,在Swift的类中,最后一个数据将只占有1个字节。


    Swift类的实际结构有点奇怪。Swift类没有类似于Objective-C的方法,这点会在后面说明。Swift类中变量被存储为一个实例变量。在Swift中利用getter和setter方法修改这个实例变量的值(译者注:此处的getter和setter并不实际存在,只是抽象为实例变量的访问和修改入口,是一种规范,在下文中不对getter和setter进行特别翻译)。奇怪的是,Swift类的实例变量其实是没有类型编码。通常应该指向类型编码的指针为NULL,这想必是因为Objective-C的运行时不会亲自去处理Swift变量。


    继承

    正如你所期望的那样,Swift同样拥有类的继承与派生。比如,在Swift中,Shape(形状)的子类Square(方形)在Objective-C类中同样是Shape的子类。然而,如果Swift中的类没有父类会怎样?


    例如:


    class Shape { } 在这种情况下,Shape类是一个SwiftObject的子类。而类swiftobject是一个类似于Objective-C中NSObject这样的一个基类。它没有父类,这意味着isa变量指向其本身。其目的是利用Swift运行时方法处理像分配和释放内存这样的事,而不是标准的Objective-C运行时。比如说,改为- (void)retain不是调用objc_retain,而是调用swift_retain


    类方法

    就像我前面提到的,Swift对象的类没有方法。相反,他们使用像C++一样的函数、重载以及其他替代。这可能是为什么Swift要比Objective-C快得多;这里不怎么需要用objc_msgsend来寻找和调用方法的实现。


    在Objective-C中,方法实现类似这样:

    type method(id self, SEL _cmd, id arg1, id arg2, …)


    Swift方法是非常相似的,但稍有不同的是参数布局,self只能作为最后一个参数传递,这里没有选择器。

    type method(id arg1, id arg2, …, id self)


    虚函数表

    就像在C++一样,Swift的类有一个虚函数表,在虚表里列出了类中的方法。它位于二进制的数据后面,看起来像这样:


    struct swift_vtable_header {

    uint32_t vtable_size;

    uint32_t unknown_000;

    uint32_t unknown_001;

    uint32_t unknown_002;

    void* nominalTypeDescriptor;

    // vtable pointers

    } 这里需要说明的是,Swift类的虚函数表只能当它在编译时可见的情况下使用。否则将会报错。


    Name Mangling(命名重整)

    Swift在各自的标志中保存元数据的函数(以及更多),这就是所谓的name mangling。该元数据包括函数名,属性、模块名称,参数类型,返回类型等等。


    class Shape{

    func numberOfSides() -> Int {

    return 5

    }

    } 以此为例:Simpledescriptio方法改编(重载)的名字是_tfc9swifttest5shape17simpledescriptionfs0_ft_si。这里分解下:

    t–所有Swift符号的前缀。所有事情皆始于次。

    F–函数。

    C一类的函数。(方法)

    9swifttest–带有一个前缀长度的模块名称。

    5shape–函数所属类的名称,带有一个前缀长度。

    17simpledescription–函数名。

    f–函数属性。在本例中,它是“f”,这是一个常见的函数。

    s0_ft–我不确定这意味着什么,但它似乎是标记参数的开始,并返回类型。

    “_”该下划线把参数类型和返回类型分别开来。由于函数没有参数,所以它直接跟在S0_FT后边。

    S–这是返回类型的开始。“S”代表Swift;返回类型是一个Swift内建类型。下一个字符决定类型。

    i–这是Swift的内建类型。一个小写的“i”,可代替int (小写的“I”代表Int)。


    函数属性





    Swift的内置命令






    命名重整并不仅仅只用于函数,但我只是给出一个简短的概述。


    函数连接

    这句子有足够的语义,让我们来到一个有趣的部分!如果我们有这样的一个类:


    class Shape {

    var numberOfSides: Int;

       init(){

       numberOfSides = 5;

       }

       }

    我们说我们想把numberofsides 改为4。有多种方式来做这个。我们可以使用MobileSubstrate连接到getter方法,并改变返回值,像这样:


    int (*numberOfSides)(id self);


    MSHook(int, numberOfSides, id self){

    return 4;

    }


    %ctor{

    numberOfSides = (int (*)(id self)) dlsym(RTLD_DEFAULT, “_TFC9swifttest5Shapeg13numberOfSidesSi”);

    MSHookFunction(numberOfSides, MSHake(numberOfSides));

    } 如果我们创建一个Shape实例,并打印出变量numberofsides的值,我们看到结果为4!这并不是很坏,是吧?现在,我知道你在想什么;“难道不是应该返回一个对象,而不是字符“4”吗?”


    的确,在Swift中,大量的内建类型都是文本。例如Int,与C中的Int一样。注意,字符串类型有点奇怪,这是一个little-endian UTF-16字符串,所以没有可以使用的C字面量。


    让我们做同样的事情,但是这一次,我们会连接setter代替getter。


    void (*setNumberOfSides)(int newNumber, id self);


    MSHook(void, setNumberOfSides, int newNumber, id self){

    _setNumberOfSides(4, self);

    }




    %ctor {

    setNumberOfSides = (void (*)(int newNumber, id self)) dlsym(RTLD_DEFAULT, “_TFC9swifttest5Shapes13numberOfSidesSi”);

    MSHookFunction(setNumberOfSides, MSHake(setNumberOfSides));

    } 再试试跑一次程序吧…结果仍然是5。你一定会问发生什么事了?这是因为,在Swift的某些地方函数是内联的。该类的构造函数是一个这样的函数。它直接设置numberofsides ivar。所以,只有数值在顶部的代码中被再次设置时才会调用setter方法。执行调用后,我们会的到4这个值。


    最后,让我们通过直接设置变量实例改变numberofsides的值。


    void (*setNumberOfSides)(int newNumber, id self);


    MSHook(void, setNumberOfSides, int newNumber, id self){

    MSHookIvarint>(self, “numberOfSides”) = 4;

    }




    %ctor {

    setNumberOfSides = (void (*)(int newNumber, id self)) dlsym(RTLD_DEFAULT, “_TFC9swifttest5Shapes13numberOfSidesSi”);

    MSHookFunction(setNumberOfSides, MSHake(setNumberOfSides));

    } 这样同样可以达到效果。虽然并不推荐这样做,但它仍是可行的解决方案。


    这就是我要写的所有东西。虽然有很多其他的东西,但我不知道是否有足够精力去写。在这篇文章中许多东西是会变化的。它们只是我使用Swift从运行时和二进制文件逆向工程得到的。但我觉得这还不错。这意味着,MobileSubstrate不会随着Objective-C消亡,仍有调整的空间。


    如果你发现更多关于Swift是如何工作的细节,不要犹豫,请让我知道!


    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    广播台
    特别关注
    快速回复 返回顶部 返回列表