美文网首页swiftswift 学习进阶
swift 进阶: 类的结构

swift 进阶: 类的结构

作者: 欧德尔丶胡 | 来源:发表于2020-12-09 16:40 被阅读0次

    swift 进阶之路:学习大纲

    前言

    • 类似于OC的源码分析,对于swift的研究,我们也从类的创建为出发点进行分析,然后分析类的底层结构,从而了解类的本质等等。

    一、类的初探

    我们先看看类的创建流程和类的大概结构

    1.0 对象的创建流程

    对象初始化

    • OC: [[HJPerosn alloc] init],一般alloc申请内存空间并创建对象,init对象进行统一初始化处理。
    • Swift: HJPerosn(),直接()就完成了对象的创建。

    开启汇编调试:

    例子一:在swift工程中,创建一个类继承NSObject
    • 通过断点调试我们会发现:这个类的创建流程开始走 OC的流程:__allocting_init ->objc_allocWithZone...... 原因就是HJPerson 继承了NSObject,原因是跟swift的派发机制有关,后面再深入的研究。
    例子二:如下不继承NSObject
    • 通过断点调试我们会发现:这个类的创建流程开始走 OC的流程:__allocting_init ->swift_allocObject;
    • 继续添加符号断点:


    最后:swift对象创建流程:
    __allocating_init -> swift_allocObject -> swift_allocObject -> swift_slowAlloc ->...

    在VSCode中的源码里,根据此流程逐渐对其源码进行分析如下:

    1.1 swift_allocObject 源码分析
    static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                           size_t requiredSize,
                                           size_t requiredAlignmentMask) {
      assert(isAlignmentMask(requiredAlignmentMask));
      auto object = reinterpret_cast<HeapObject *>(
          swift_slowAlloc(requiredSize, requiredAlignmentMask));//分配内存+字节对齐
    
     //注意:这依赖于c++ 17保证的无空指针语义
    //检查我们在Windows上观察到的新分配器的位置,
    // Linux和macOS。
      new (object) HeapObject(metadata);//初始化一个实例对象
    
      //如果启用了泄漏跟踪,请开始跟踪此对象。
      SWIFT_LEAKS_START_TRACKING_OBJECT(object);
    
      SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);
    
      return object;
    }
    
    • swift_allocObject的源码如下,主要有以下几部分

      • 通过swift_slowAlloc分配内存,并进行内存字节对齐
      • 通过new + HeapObject + metadata初始化一个实例对象
      • 函数的返回值是HeapObject类型,所以当前对象的内存结构就是HeapObject的内存结构
    1.2 swift_allocObject 源码分析
    void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
      void *p;
      //这个检查也强制“默认”对齐使用AlignedAlloc。  if (alignMask <= MALLOC_ALIGN_MASK) {
    #if defined(__APPLE__)
        p = malloc_zone_malloc(DEFAULT_ZONE(), size);
    #else
        p = malloc(size);// 堆中创建size大小的内存空间,用于存储实例变量
    #endif
      } else {
        size_t alignment = (alignMask == ~(size_t(0)))
                               ? _swift_MinAllocationAlignment
                               : alignMask + 1;
        p = AlignedAlloc(size, alignment);
      }
      if (!p) swift::crash("Could not allocate memory.");
      return p;
    }
    
    • 进入swift_slowAlloc函数,其内部主要是通过malloc_zone_malloc在堆中分配size大小的内存空间,并返回内存地址,主要是用于存储实例变量
    1.3 查看HeapObject 并 计算类的大小
    // The members of the HeapObject header that are not shared by a
    // standard Objective-C instance
    #define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
      InlineRefCounts refCounts ///引用计数
    
    /// The Swift heap-object header.
    /// This must match RefCountedStructTy in IRGen.
    struct HeapObject {
      /// This is always a valid pointer to a metadata object.
      HeapMetadata const *metadata;/// 元数据
    
      SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS; ///引用计数
    
    
    • metadata:是 HeapMetedata类型
    • refCounts : 引用计数

    【总结】

    • Swift中实例对象,默认的比OC中多了一个refCounted引用计数大小,默认属性占16字节 : metadata(struct)8字节和refCounts(class)8字节
    • OC中实例对象的本质是结构体,是以objc_object为模板继承的,其中有一个isa指针,占8字节
    1.4【验证+拓展】
    //验证
    class HJPerson {
    }
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            print(class_getInstanceSize(HJPerson.self))
        }
    }
    打印 : 16
    
    //拓展
    class HJPerson {
        var age : Int = 20
        var name : String = "HJ"
    }
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            print(class_getInstanceSize(HJPerson.self))
        }
    }
    打印 : 40
    

    验证的确一个空类的大小为16;那么为啥第二个是40呢?
    我们通过打印Int 和 String 内存大小来验证

    //********* Int底层定义 *********
    @frozen public struct Int : FixedWidthInteger, SignedInteger {...}
    
    //String底层定义
    @frozen public struct String {...}
    
    //验证 
    print(MemoryLayout<Int>.stride)
    print(MemoryLayout<String>.stride)
    
    打印
    8
    16
    
    • 从打印的结果中可以看出,Int类型占8字节,String类型占16字节,这点与OC中是有所区别的 ,以后再进行详细讲解吧。

    • 所以这也解释了为什么HJPerson的内存大小等于40,即40 = metadata(8字节) +refCount(8字节)+ Int(8字节)+ String(16字节)

    二、Swift中类的结构探索

    • 在Swift中,类的结构在底层是HeapObject,其中有metadata +refCounts
    2.1 metadata的底层探索
    • 进入HeapMetadata定义,是TargetHeapMetaData类型的别名,接收了一个参数Inprocess
    using HeapMetadata = TargetHeapMetaData<Inprocess>;
    
    • 进入TargetHeapMetaData定义,其本质是一个模板类型,其中定义了一些所需的数据结构。这个结构体中没有属性,只有初始化方法,传入了一个MetadataKind类型的参数(该结构体没有,那么只有在父类中了)这里的kind就是传入的Inprocess
    //模板类型
    template <typename Runtime>
    struct TargetHeapMetadata : TargetMetadata<Runtime> {
      using HeaderType = TargetHeapMetadataHeader<Runtime>;
    
      TargetHeapMetadata() = default;
      //初始化方法
      constexpr TargetHeapMetadata(MetadataKind kind)
        : TargetMetadata<Runtime>(kind) {}
    
    };
    
    • 进入TargetMetaData定义,有一个kind属性,kind的类型就是之前传入的Inprocess。从这里可以得出,对于kind,其类型就是unsigned long,主要用于区分是哪种类型的元数据
    // TargetMetaData 定义 
    struct TargetMetaData{
       using StoredPointer = typename Runtime: StoredPointer;
        ...
        
        StoredPointer kind;
    }
    
    // Inprocess 定义
    struct Inprocess{
        ...
        using StoredPointer = uintptr_t;
        ...
    }
    
    //******** uintptr_t 定义 ********
    typedef unsigned long uintptr_t;
    
    • 可以看出初始化方法中参数kind的类型是MetadataKind,
    2.2 getClassObject
    • 回到TargetMetaData结构体定义中,找方法getClassObject
     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;
       }
     }
    
    • 在该方法中去匹配kind返回值是TargetClassMetadata类型

    通过lldb来验证

    • po metadata->getKind(),得到其kind是Class
    • po metadata->getClassObject()、x/8g 0x0000000110efdc70,这个地址中存储的是元数据信息!

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

    2.3 TargetClassMetadata

    进入TargetClassMetadata定义,继承自TargetAnyClassMetadata,有以下这些属性,这也是类结构的部分

    template <typename Runtime>
    struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
        ...
        //swift特有的标志
        ClassFlags Flags;
        //实力对象内存大小
        uint32_t InstanceSize;
        //实例对象内存对齐方式
        uint16_t InstanceAlignMask;
        //运行时保留字段
        uint16_t Reserved;
        //类的内存大小
        uint32_t ClassSize;
        //类的内存首地址
        uint32_t ClassAddressPoint;
      ...
    }
    
    • 当前类返回的实际类型是 TargetClassMetadata,而TargetMetaData中只有一个属性kind,TargetAnyClassMetaData中有3个属性,分别是kind, superclass,cacheData
    • 当前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 flags; //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;
        ...
    }
    

    三、分析思路整理

    四、swif与OC 类的结构对比

    • 实例对象 & 类

      • OC中的实例对象本质是结构体,是通过底层的objc_object模板创建,类是继承自objc_class

      • Swift中的实例对象本质也是结构体,类型是HeapObject,比OC多了一个refCounts

    • 引用计数

      • OC中的ARC维护的是散列表

      • Swift中的ARC是对象内部有一个refCounts属性

    • 方法列表
      • OC中的方法存储在objc_class结构体class_rw_tmethodList

      • swift中的方法存储在 metadata 元数据中

    相关文章

      网友评论

        本文标题:swift 进阶: 类的结构

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