美文网首页
Swift进阶(二)--- 类,对象

Swift进阶(二)--- 类,对象

作者: Jax_YD | 来源:发表于2020-12-13 20:40 被阅读0次

    Swift编译简介

    我们先来开一段简单的代码:

    class SuperMan  {
        var age:Int = 18
        var name:String = "Aaron"
    }
    let t = SuperMan()
    

    我们创建一个SuperMan类,并通过默认的初始化器,创建一个实例对象并赋值给了t
    那么问题来了:这个默认的初始化器到底做了一个什么样的操作?
    这里我们引入SIL(Swift intermediate language),在阅读SIL的代码之前,我们先来了解一下什么是SIL

    首先不管是OC还是Swift,后端都是通过LLVM进行编译的,如下图所示:

    image.png
    可以看到:
    OC通过clang编译器,编译成IR,然后再生成可执行文件.o(这里也就是我们的机器码)
    Swift则是通过Swift 编译器编译成IR,然后再生成可执行文件
    我们再来看一下,一个swift文件的编译过程都经历了哪些步骤:
    image.png

    swift在编译过程中使用的前端编译器是swiftc,和我们之前在OC中使用的Clang是有所区别的。我们可以通过swiftc -h命令,来查看swiftc都能做什么事情

    image.png

    SIL

    SIL参考文档

    • 首先进入工程所在的文件夹,然后执行swiftc -emit-sil main.swift | xcrun swift-demangle >> ./main.sil && code main.sil 指令。对该指令有疑问的同学,可以点击这里,链接文章的最后,有对该指令的详细讲解。下方代码是用VSCode打开的main.sil文件,
    // t
    sil_global hidden @main.t : main.SuperMan : $SuperMan
    
    // main
    // '@main': 标识当前main.swift的入口函数。SIL中的标识符名称以‘@’作为前缀
    sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
    //'%0、%1' 在SIL中叫做寄存器,可以理解为开发中的常量,一旦赋值就不可修改,如果想继续使用,就需要不算的累加数字(注意:这里的寄存器指的是‘虚拟寄存器’,与汇编指令‘register read %x’读取的寄存器不同,汇编指令读取的是真实的寄存器)
    bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
    //‘alloc_global’ 创建一个‘全局变量’ --> ‘t
      alloc_global @main.t : main.SuperMan           // id: %2
    //‘global_addr’ 获取全局变量的地址,并赋值给%3
      %3 = global_addr @main.t : main.SuperMan : $*SuperMan // user: %7
    //‘metatype’ 获取‘Superman’ 的 ‘MetaData’ 赋值给 %4
      %4 = metatype $@thick SuperMan.Type             // user: %6
      // function_ref SuperMan.__allocating_init()
    //将 ‘__allocating_init()’ 函数的地址赋值给 %5
      %5 = function_ref @main.SuperMan.__allocating_init() -> main.SuperMan : $@convention(method) (@thick SuperMan.Type) -> @owned SuperMan // user: %6
    //’apply‘ 调用 ’__allocating_init()‘ 并将返回值 赋值给 %6  
      %6 = apply %5(%4) : $@convention(method) (@thick SuperMan.Type) -> @owned SuperMan // user: %7
    //’store‘ 将 %6 的值 存储到 %3,也就是前面提到的全局变量的地址
      store %6 to %3 : $*SuperMan                     // id: %7
    //创建一个整数文字值,类型为Builtin.Int32,该类型必须为内置整数类型。文字值是使用Swift的整数文字语法指定的,值为0,使用者%8
      %8 = integer_literal $Builtin.Int32, 0          // user: %9
    //构建结构体
      %9 = struct $Int32 (%8 : $Builtin.Int32)        // user: %10
      return %9 : $Int32                              // id: %10
    } // end sil function 'main'
    
    • 下面我们结合汇编进行一下断点调试。查看一下SuperMan的创建流程,如下图打断点
      image.png
      Dubge中选择Always Show Disassembly
      image0.png
      接下来我们会在汇编代码中发现一个SuperMan.__allocating_init()
      image1.png
      我们使用si指令跟进去会发现该方法的内部会调用SuperMan.init()函数,并且会先调用swift_allocObject函数。
      image2.png

    源码调试

    根据上面的分析,接下来我们通过swift_allocObject来探索swift中对象的创建过程,通过搜索swift_allocObject我们找到了该函数的实现

    image3.png
    • 接下来我们来进行断点调试


      image4.png
    • 可以看到左侧local有详细的信息
    • 其中requiredSize是分配的实际内存大小,为40。其中metadata:8, refCounts:8, age:8, name:16。
    • requiredAligmentMask是swift中的字节对其方式,这个和OC中的是一样的,必须是8的倍数,不足的会自动补齐,目的是以空间换时间,来提高内存的操作效率。
      注意:这里的requiredSizerequiredAligmentMask都是上层函数传进来的

    swift_allocObject 源码分析

    swift_allocObject的源码如下,主要有一下几部分组成

    • 通过swift_slowAlloc分配内存,并进行内存字节对齐。
    • 通过new + HeapObject + metadata 初始化一个实例对象
    • 函数的返回值是HeapObject 类型,所以当前对象的内存结构就是HeapObject的内存结构
      image5.png
    • 进入swift_slowAlloc函数,其内部主要是通过malloc中分配size大小的内存空间,并返回内存地址,主要是用于存储实例变量
      image6.png
    • 进入HeapObject初始化方法,可以看到两个参数:metadata、refCounts
      image7.png
    • 其中metadata类型是HeapMetadata,是一个指针类型,占8个字节
    • refCounts(引用计数,类型是InlineRefCounts,而InlineRefCounts是一个RefCounts的别名,占8个字节)
    typedef RefCounts<InlineRefCountBits> InlineRefCounts;
    
    image8.png

    总结

    • 对于实例对象t来说,其本质是一个HeapObject结构体,默认16字节内存大小(metadata8字节 + refCounts8字节),与OC对比如下:
      • OC中实例对象的本质是结构体,是以objc_object为模板继承的,其中有一个isa指针,占8字节。
      • Swift中实例对象,默认的比OC中多一个refCounts引用计数,默认属性占16字节。
    • Swift中对象的分配流程是:__allocating_init--->swift_allocObject--->_swift_allocObject_--->swift_slowAlloc-->malloc
    • init在其中的职责就是初始化变量,这点与OC中的是一致的。

    看到这里,有的同学可能会有一些疑问❓:为什么 agename分别是 8字节16字节?
    接下来我们详细讲解一下:
    对于IntString类型,在Swift中,两个都是结构体类型。我可以通过内存打印来验证一下

    //********* Int底层定义 *********
    @frozen public struct Int : FixedWidthInteger, SignedInteger {...}
    
    //********* String底层定义 *********
    @frozen public struct String {...}
    
    //********* 验证 *********
    print("Int类型所占内存大小:\(MemoryLayout<Int>.stride)")
    print("String类型所占内存大小:\(MemoryLayout<String>.stride)")
    
    print("t.age所占内存大小:\(MemoryLayout.size(ofValue: t.age))")
    print("t.name所占内存大小:\(MemoryLayout.size(ofValue: t.name))")
    //********* 打印结果 *********
    Int类型所占内存大小:8
    String类型所占内存大小:16
    t.age所占内存大小:8
    t.name所占内存大小:16
    

    通过上面的打印结果,可以清楚的看到IntString所占内存大小。

    但是到这里,可能同学问了,那如果String的值非常大的话,内存里面分配的16个自己又该怎么存储呢?
    swift的内存分配的问题,之后会单独写一篇文章来详细讲一下,这里就不做详细的赘述,这里记住Int占8个字节,String占16个字节就可以了。其它的类型,可自行打印出来看一下。

    下面我们来看另一个问题。我们上面提到了metadata,那它到底是什么呢?我们继续往下分析

    Swift中 类结构的探索

    在OC中类是从objc_class模板继承过来的。
    在Swift中,类的结构在底层是HeapObject,包含metadatarefCounts
    metadata的类型是HeapMetadata

    HeapMetadata类型分析

    • 通过Swift源码,跟进去我们会发现
    using HeapMetadata = TargetHeapMetadata<InProcess>
    
    • 我们接着跟进TargetHeapMetadata
    template <typename Runtime>
    struct TargetHeapMetadata : TargetMetadata<Runtime> {
      using HeaderType = TargetHeapMetadataHeader<Runtime>;
    
      TargetHeapMetadata() = default;
    //初始化方法
      constexpr TargetHeapMetadata(MetadataKind kind)
        : TargetMetadata<Runtime>(kind) {}
    #if SWIFT_OBJC_INTEROP
      constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
        : TargetMetadata<Runtime>(isa) {}
    #endif
    };
    
    • 进入TargetHeapMetadata定义后,我们发现其本质是一个模板类型。这个结构体中没有属性,只有初始化方法,传入一个MetadataKind类型的参数,这里kind就是传入的Inprocess

    • 接下来,我们继续跟进到TargetMetadata中,有一个kind的属性,kind的类型就是前面传入的Inprocess。然后我们再跟进Inprocess,最总得到结果是:对于kind,其类型的本质是unsigned long

    //******** TargetMetaData 定义 ********
    struct TargetMetadata {
      using StoredPointer = typename Runtime::StoredPointer
      ...
      private:
      /// The kind. Only valid for non-class metadata; getKind() must be used to get
      /// the kind value.
      StoredPointer Kind
    }
    //******** Inprocess 定义 ********
    struct InProcess {
      static constexpr size_t PointerSize = sizeof(uintptr_t);
      using StoredPointer = uintptr_t;
      ...
    }
    //******** uintptr_t 定义 ********
    typedef unsigned long           uintptr_t;
    
    • TargetHeapMetadataTargetMetadata定义中,我可以看出kind的类型是MetadataKind(此处是强制转化,与上文并不冲突)
    • 跟进MetadataKind,可以看到一个#include "MetadataKind.def",再次跟进,会看到所有类型的元数据
      image9.png
      总结之后得到如下表格:
    name Value
    Class 0x0
    Struct 0x200
    Enum 0x201
    Optional 0x202
    ForeignClass 0x203
    Opaque 0x300
    Tuple 0x301
    Function 0x302
    Existential 0x303
    Metatype 0x304
    ObjCClassWrapper 0x305
    ExistentialMetatype 0x306
    HeapLocalVariable 0x400
    HeapGenericLocalVariable 0x500
    ErrorObject 0x501
    LastEnumerated 0x7FF
    • 回到TargetMetadata结构体定义中,找到方法getClassObject, 在该方法中去匹配kind,返回值TargetClassMetadata类型
    • 如果是Class,则直接对this(当前指针,即metadata) 强转为ClassMetadata
      /// Get the class object for this type if it has one, or return null if the
      /// type is not a class (or not a class with a class object).
      const TargetClassMetadata<Runtime> *getClassObject() const
    
    //*************  方法的实现 **************
      template<> inline const ClassMetadata *
      Metadata::getClassObject() const {
        //匹配kind
        switch (getKind()) {
        //如果kind是class
        case MetadataKind::Class: {
          // Native Swift class metadata is also the class object.
          // 将当前指针强转为ClassMetadata类型
          return static_cast<const ClassMetadata *>(this);
        }
        case MetadataKind::ObjCClassWrapper: {
          // Objective-C class objects are referenced by their Swift metadata wrapper.
          auto wrapper = static_cast<const ObjCClassWrapperMetadata *>(this);
          return wrapper->Class;
        }
        // Other kinds of types don't have class objects.
        default:
          return nullptr;
        }
      
    //************** ClassMetadata ****************
    using ClassMetadata = TargetClassMetadata<InProcess>;
    

    所以,TargetMetadataTargetClassMetadata 本质上是一样的,因为在内存结构中,可以直接进行指针的转换,所以我们可以认为:结构体其实就是TargetClassMetadata

    • 进入TargetClassMetadata定义,发现其继承自TargetAnyClassMetadata
    template <typename Runtime>
    struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
    ...
    /// Swift-specific class flags. 
    ///Swift 特有的标识
      ClassFlags Flags;
    
      /// The address point of instances of this type. 
      ///此类型的实例对象的地址
      uint32_t InstanceAddressPoint;
    
      /// The required size of instances of this type.
      /// 'InstanceAddressPoint' bytes go before the address point;
      /// 'InstanceSize - InstanceAddressPoint' bytes go after it.
     /// 实例对象内存大小
      uint32_t InstanceSize;
    
      /// The alignment mask of the address point of instances of this type.
      /// 实例对象内存对齐
      uint16_t InstanceAlignMask;
    
      /// Reserved for runtime use.
      /// 运行时保留字段
      uint16_t Reserved;
    
      /// The total size of the class object, including prefix and suffix
      /// extents.
      /// 类的内存大小
      uint32_t ClassSize;
    
      /// The offset of the address point within the class object.
      /// 类的内存首地址
      uint32_t ClassAddressPoint;
    ...
    }
    
    • 进入TargetAnyClassMetadata,又会发现,其继承自TargetHeapMetadata
    template <typename Runtime>
    struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
      using StoredPointer = typename Runtime::StoredPointer;
      using StoredSize = typename Runtime::StoredSize
    ...
    }
    

    总结

    综上所述,当metadatakind为Class时,有如下继承关系:

    image10.png
    • 当前类返回的实际类型是TargetClassMetadata;从上面继承链可以看出 TargetMetadata只有一个属性kindTargetAnyClassMetaData中有4个属性:kindsuperClasscacheDatadata
    • 当前Class在内存中存放的属性,是由TargetClassMetadata + TargetAnyClassMetadata + TargetMetadata 的属性加起来构成的,因此得出metadata的数据结构体如下:
    struct swift_class_t:NSObject {
        void *kind;//相当于OC中的isa,kind的实际类型是unsigned long
        void *superClass;
        void *cacheData;
        void *data;
        uint32_t flages; //4字节
        uint32_t instanceAddressOffset; //4字节
        uint32_t instanceSize; //4字节
        uint16_t instanceAlignMask; //2字节
        uint16_t reserved; //2字节
    
        uint32_t classSize; //4字节
        uint32_t classAddressOffset; //4字节
        void *description
        ...
    }
    

    与OC对比

    • 实例对象 & 类
      • OC中的实例对象本质是结构体,是通过底层的objc_object模板创建的,类是继承自objc_class
      • Swift中的实例对象本质也是结构体,类型是HeapObject,比OC多了一个refCounts
    • 方法列表
      • OC中的方法储存在objc_class结构体class_rw_tmethodList
      • Swift中的方法储存在metadata元数据中
    • 引用计数
      • OC中的ARC维护的是散列表
      • Swift中的ARC是对象内部有一个refCounts属性

    相关文章

      网友评论

          本文标题:Swift进阶(二)--- 类,对象

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