Swift中 Class和Struct的底层结构

该篇主要是关于研究Class和Struct的底层结构,以及Swift反射的相关知识。

1.Struct和Class的内存分布

class Size {
    var width = 1
    var height = 2
}

struct Point {
    var x = 3
    var y = 4
}

众所周知,结构体的值是直接储存在栈空间,类的值是储存在堆空间,栈空间是堆的地址指针。

类和结构体内存分布.jpg

那我们所研究的底层结构又是什么?储存在哪里?

2. 从Mirror的解析开始

说要获取底层结构,不得不先介绍Mirror。不同于OC的动态性质,可以通过Runtime获取到对象的属性类型等信息。swift标准库提供了反射机制,用来访问成员信息,即Mirror。
Mirror反射是指可以动态获取类型、成员信息,在运行时可以调用方法、属性等行为的特性。

class Person {
    var name: String = "xiaohei"
    var age: Int = 18
    var height = 1.85
}

var p = Person()
var mirror = Mirror(reflecting: p.self)

print("对象类型:\(mirror.subjectType)")
print("对象属性个数:\(mirror.children.count)")
print("对象的属性及属性值")
for child in mirror.children {
    print("\(child.label!)---\(child.value)")
}

通过Mirror,我们可以更简单的获取到对象的属性值。那系统是如何通过Mirror获取对应的属性以及值的?
通过源码分析,我们可以看到我们所需要的信息都是从Mirror这个结构体获取的。在我看来,Mirror更像一个包装类,因为它包装了更底层结构的东西。

public struct Mirror {
  public enum DisplayStyle {
    case `struct`, `class`, `enum`, tuple, optional, collection
    case dictionary, `set`
  }
    public let subjectType: Any.Type
    public let children: Children
    public let displayStyle: DisplayStyle?
    public var superclassMirror: Mirror? {
      return _makeSuperclassMirror()
    }

    // 初始化方法
    public init(reflecting subject: Any) {
    if case let customized as CustomReflectable = subject {
      self = customized.customMirror
    } else {
      self = Mirror(internalReflecting: subject)
    }
  }
}

Mirror的实现是由一部分Swift代码加上另一部分C++代码。很多代码就不截取了,多了反而乱,这边主要讲的是思路,具体解析文章可以参考这篇《Swift中的反射Mirror》
Mirror内部的初始化方法是通过实例(包括对象,结构体等)创建的,获取类型通过_getNormalizedType,获取count 通过_getChildCount。

 internal init(internalReflecting subject: Any,
              subjectType: Any.Type? = nil,
              customAncestor: Mirror? = nil)
  {
    let subjectType = subjectType ?? _getNormalizedType(subject, type: type(of: subject))
    
    let childCount = _getChildCount(subject, type: subjectType)
    let children = (0 ..< childCount).lazy.map({
      getChild(of: subject, type: subjectType, index: $0)
    })
    self.children = Children(children)
    
    self._makeSuperclassMirror = {
      guard let subjectClass = subjectType as? AnyClass,
            let superclass = _getSuperclass(subjectClass) else {
        return nil
      }
      
      // Handle custom ancestors. If we've hit the custom ancestor's subject type,
      // or descendants are suppressed, return it. Otherwise continue reflecting.
      if let customAncestor = customAncestor {
        if superclass == customAncestor.subjectType {
          return customAncestor
        }
        if customAncestor._defaultDescendantRepresentation == .suppressed {
          return customAncestor
        }
      }
      return Mirror(internalReflecting: subject,
                    subjectType: superclass,
                    customAncestor: customAncestor)
    }
    
    let rawDisplayStyle = _getDisplayStyle(subject)
    switch UnicodeScalar(Int(rawDisplayStyle)) {
    case "c": self.displayStyle = .class
    case "e": self.displayStyle = .enum
    case "s": self.displayStyle = .struct
    case "t": self.displayStyle = .tuple
    case "\0": self.displayStyle = nil
    default: preconditionFailure("Unknown raw display style '\(rawDisplayStyle)'")
    }
    
    self.subjectType = subjectType
    self._defaultDescendantRepresentation = .generated
  }

比如获取类型的底层实现来说,其本质就是获取ReflectionMirrorImpl对应的type(当然,更下面还有一层,Impl通常也只作为一层包装)。

// func _getNormalizedType<T>(_: T, type: Any.Type) -> Any.Type
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
const Metadata *swift_reflectionMirror_normalizedType(OpaqueValue *value,
                                                      const Metadata *type,
                                                      const Metadata *T) {
  return call(value, T, type, [](ReflectionMirrorImpl *impl) { return impl->type; });
}

ReflectionMirrorImpl 是一个“基类”,提供了一些基础的属性。ReflectionMirrorImpl有以下6个子类:

TupleImpl 元组的反射
StructImpl 结构体的反射
EnumImpl 枚举的反射
ClassImpl 类的反射
MetatypeImpl 元数据的反射
OpaqueImpl 不透明类型的反射

所以终究来说,比如结构体的Mirror获取的是对应的StructImpl的东西。这边我们也以结构体为例,来讲解结构体反射的思路。

// Implementation for structs.
struct StructImpl : ReflectionMirrorImpl {
 bool isReflectable() {
   const auto *Struct = static_cast<const StructMetadata *>(type);
   const auto &Description = Struct->getDescription();
   return Description->isReflectable();
 }

 // 表明是结构体
 char displayStyle() {
   return 's';
 }
 // 获取属性个数
 intptr_t count() {
   if (!isReflectable()) {
     return 0;
   }

   auto *Struct = static_cast<const StructMetadata *>(type);
   return Struct->getDescription()->NumFields;
 }
 // 获取每个属性的偏移值
 intptr_t childOffset(intptr_t i) {
   auto *Struct = static_cast<const StructMetadata *>(type);

   if (i < 0 || (size_t)i > Struct->getDescription()->NumFields)
     swift::crash("Swift mirror subscript bounds check failure");

   // Load the offset from its respective vector.
   return Struct->getFieldOffsets()[i];
 }

 const FieldType childMetadata(intptr_t i, const char **outName,
                               void (**outFreeFunc)(const char *)) {
   StringRef name;
   FieldType fieldInfo;
   std::tie(name, fieldInfo) = getFieldAt(type, i);
   assert(!fieldInfo.isIndirect() && "indirect struct fields not implemented");
   
   *outName = name.data();
   *outFreeFunc = nullptr;
   
   return fieldInfo;
 }
 // 可以获取到属性名称和属性偏移的指针,也就是属性值。
 AnyReturn subscript(intptr_t i, const char **outName,
                     void (**outFreeFunc)(const char *)) {
   auto fieldInfo = childMetadata(i, outName, outFreeFunc);

   auto *bytes = reinterpret_cast<char*>(value);
   auto fieldOffset = childOffset(i);
   auto *fieldData = reinterpret_cast<OpaqueValue *>(bytes + fieldOffset);

   return copyFieldContents(fieldData, fieldInfo);
 }
};

首先一个判断是否支持反射的方法,最中是访问的Description->isReflectable()。
isReflectable 的实现来看,我们可以看出最根本的数据结构StructMetadata。获取属性个数和偏移值也是同样的思路。而且都是通过Struct->getDescription()获取到Description所获取的,关于Description有一系列继承关系,这边我就不一一解释,我们可以从下图的继承链中,看出Description存储着很多信息(基本包括所有我们想要的信息)。

StructDescriptor继承链.png

类型名称,属性数量,属性偏移量这些都是比较常用的,我们重点讲解Fields。
Fields是一个指针,指向一个FieldDescriptor。类似Description,FieldDescriptor存储着属性的相关信息。仿写的结构体如下:

struct FieldDescriptor {

    enum FieldDescriptorKind: UInt16 {
        case Struct
        case Class
        case Enum
        // Fixed-size multi-payload enums have a special descriptor format that encodes spare bits.
        case MultiPayloadEnum
        // A Swift opaque protocol. There are no fields, just a record for the type itself.
        case kProtocol
        // A Swift class-bound protocol.
        case ClassProtocol
        // An Objective-C protocol, which may be imported or defined in Swift.
        case ObjCProtocol
        // An Objective-C class, which may be imported or defined in Swift.
        // In the former case, field type metadata is not emitted, and must be obtained from the Objective-C runtime.
        case ObjCClass
    }

    var MangledTypeName: RelativeDirectPointer<CChar>//类型命名重整
    var Superclass: RelativeDirectPointer<CChar>//父类名
    var Kind: FieldDescriptorKind//类型,看枚举
    var FieldRecordSize: Int16 //这个值乘上NumFields会拿到RecordSize
    var NumFields: Int32//还是属性个数

    //获取每个属性,得到FieldRecord
    mutating func getField(index: Int) -> UnsafeMutablePointer<FieldRecord> {
        return withUnsafeMutablePointer(to: &self) {
            let arrayPtr = UnsafeMutableRawPointer($0.advanced(by: 1)).assumingMemoryBound(to: FieldRecord.self)
            return arrayPtr.advanced(by: index)
        }
    }
}

FieldRecord是每个属性的信息,包含属性名,属性的性质。通过Index,从FieldDescriptor获取到对应的属性信息。那属性值哪里获取呢?按照我的理解,结构体的元数据是存储在堆上的,用于创建实例,实例的溯源。属性值是不固定的,属于实例的信息,存储在栈上的。所以属性值得另外通过偏移值去获取。

struct FieldRecord {
    struct FieldRecordFlags {
        var Data: UInt32
        /// Is this an indirect enum case?
        func isIndirectCase() -> Bool {
            return (Data & 0x1) == 0x1;
        }

        /// Is this a mutable `var` property?
        func isVar() -> Bool {
            return (Data & 0x2) == 0x2;
        }
    }

    var Flags: FieldRecordFlags //标记位
    var MangledTypeName: RelativeDirectPointer<CChar>//类型命名重整
    var FieldName: RelativeDirectPointer<CChar>//属性名
}

具体怎么获取属性的值呢?有如下步骤:(具体代码有空再补上,这段也是抄的哈哈)

  • 首先获取FieldOffsetVectorOffset的值
  • 然后在加上this也就是当前Metadata的指针
  • 这里我们将仿写的StructMetadata的指针ptr重绑定为Int
  • 源码中加上FieldOffsetVectorOffset,这里我们就移动FieldOffsetVectorOffset
  • 然后将上述移动后的绑定为一个Int32的指针
  • 最后使用UnsafeBufferPointer和属性个数创建一个buffer数组指针
  • 接下来我们就可以从数组中取出每个属性的偏移值
  • 然后取出结构体实例p的内存地址
  • 然后按照buffer数组中的偏移值进行偏移,重绑定为属性的类型
  • 最后就可以打印出属性值了

所以,我们可以总结一下:
Mirror是一个包装类,通过传入的对象类型获取对应的ReflectionMirrorImpl,比如结构体对应的Impl类型为StructImpl,StructImpl会在内部创建一个StructMetadata。Mirror获取属性值都是间接从StructMetadata获取,属性名,属性类型这一类固定的信息可以直接获取,属性值是通过StructMetadata获取的属性偏移地址获取的。

Mirror解析流程.png

3. Struct的底层结构

在上面Mirror的解析中,已经对Struct的底层结构有了进一步研究。
很明显,再概括性的解释下StructMetaData,Struct的底层结构就清晰可见了。

如上一部分Mirror解析所说的,Mirror底层的核心逻辑,就是通过getKind()方法获取该类型的元数据类型,然后根据该类型Metadata获取相应的属性,比如类型名称、属性名字,属性个数等。


MetadataKind.png

每种类型都有对应的Metadata,所以研究Struct或者Class都是通过对应的Metadata来研究的。


MetadataKind对应表.png

通过对StructMetadata源码的解析,我们可以概括(仿写)下StructMetadata的结构,这样便于理解。

struct StructMetadata {
    var Kind: InProcess   // MetadataKind,结构体的枚举值是0x200
    var Description: UnsafeMutablePointer<TargetStructDescriptor>// 结构体的描述,包含了结构体的所有信息,是一个指针
    
    //获得每个属性的在结构体中内存的起始位置、
    mutating func getFieldOffset(index: Int) -> Int {
        if Description.pointee.NumFields == 0 {
            print("结构体没有属性")
            return 0
        }
        let fieldOffsetVectorOffset = self.Description.pointee.FieldOffsetVectorOffset
        return withUnsafeMutablePointer(to: &self) {
            //获得自己本身的起始位置
            let selfPtr = UnsafeMutableRawPointer($0).assumingMemoryBound(to: InProcess.self)
            //以指针的步长偏移FieldOffsetVectorOffset
            let fieldOffsetVectorOffsetPtr = selfPtr.advanced(by: numericCast(fieldOffsetVectorOffset))
            //属性的起始偏移量已32位整形存储的,转一下指针
            let tramsformPtr = UnsafeMutableRawPointer(fieldOffsetVectorOffsetPtr).assumingMemoryBound(to: UInt32.self)
            return numericCast(tramsformPtr.advanced(by: index).pointee)
        }
    }
}

可以看出StructMetadata主要由Kind和Description组成,Kind用来表明自己是一个Struct的Metadata。Description则隐藏着Struct的各种信息。
那Description的具体结构又有什么?仿写的TargetStructDescriptor结构如下:

struct TargetStructDescriptor {
    // 存储在任何上下文描述符的第一个公共标记
    var Flags: ContextDescriptorFlags

    // 复用的RelativeDirectPointer这个类型,其实并不是,但看下来原理一样
    // 父级上下文,如果是顶级上下文则为null。获得的类型为InProcess,里面存放的应该是一个指针,测下来结构体里为0,相当于null了
    var Parent: RelativeDirectPointer<InProcess>

    // 获取Struct的名称
    var Name: RelativeDirectPointer<CChar>

    // 这里的函数类型是一个替身,需要调用getAccessFunction()拿到真正的函数指针(这里没有封装),会得到一个MetadataAccessFunction元数据访问函数的指针的包装器类,该函数提供operator()重载以使用正确的调用约定来调用它(可变长参数),意外发现命名重整会调用这边的方法(目前不太了解这块内容)。
    var AccessFunctionPtr: RelativeDirectPointer<UnsafeRawPointer>

    // 一个指向类型的字段描述符的指针(如果有的话)。类型字段的描述,可以从里面获取结构体的属性。
    var Fields: RelativeDirectPointer<FieldDescriptor>
    // 结构体属性个数
    var NumFields: Int32
    // 存储这个结构的字段偏移向量的偏移量(记录你属性起始位置的开始的一个相对于metadata的偏移量,具体看metadata的getFieldOffsets方法),如果为0,说明你没有属性
    var FieldOffsetVectorOffset: Int32
}

//这个类型是通过当前地址的偏移值获得真正的地址,有点像文件目录,用当前路径的相对路径获得绝对路径。
struct RelativeDirectPointer<T> {
    var offset: Int32 //存放的与当前地址的偏移值

    //通过地址的相对偏移值获得真正的地址
    mutating func get() -> UnsafeMutablePointer<T> {
        let offset = self.offset
        return withUnsafeMutablePointer(to: &self) {
            return UnsafeMutableRawPointer($0).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self)
        }
    }
}

举个例子🌰:

struct Teacher: Codable {
    var name = "Tom"
    var age = 23
    var city = "ShangHai"
    var height = 175
}

// 通过源码我们可以知道Type类型对应的就是Metadata,这里记住要转成Any.Type,不然typesize不一致,不让转
let ptr = unsafeBitCast(Teacher.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)
print("0x\(String(ptr.pointee.Kind.PointerSize, radix: 16))") //kind枚举值是0x200,代表着结构体

let descriptionptr = ptr.pointee.Description

let Flags = descriptionptr.pointee.Flags
print(Flags.getContextDescriptorKind()!)  // 公共标记中获取kind为Struct

let ParentPtr = descriptionptr.pointee.Parent.get()
print(ParentPtr.pointee.PointerSize) // 结果为0,说明已经是顶级上下文了

let structName = descriptionptr.pointee.Name.get()
print(String(cString: structName)) // 拿到Teacher字符串

//拿到属性个数,属性名字,属性在内存的起始位置,这样就可以取值,mirror的原理就是这个!!
let propertyCount = Int(descriptionptr.pointee.NumFields)
print("属性个数:\(propertyCount)")
print("---------")
(0..<propertyCount).forEach {
    let propertyPtr = descriptionptr.pointee.Fields.get().pointee.getField(index: $0)
    print("""
        属性名:\(String(cString: propertyPtr.pointee.FieldName.get()))
        起始位置:\(ptr.pointee.getFieldOffset(index: $0))
        类型命名重整:\(String(cString: propertyPtr.pointee.MangledTypeName.get()))
        是否是var修饰的变量:\(propertyPtr.pointee.Flags.isVar() ? "是" : "否" )
        ---------
        """)
}

4.Class的底层结构

类似StructMetaData,Class的底层结构是ClassMetadata。当然不是这么一个简单的结构,是有层层继承链的(TargetClassMetadata,TargetAnyClassMetadata)。

ClassMetaData继承链.png

不同层有着不同的属性。所以这边不打算理清它们的继承关系,简单地总结下ClassMetadata的相关属性。

// Swift中的class的metadata兼容OC的类
struct ClassMetadata {
    // 在oc中放的就是isa,在swift中kind大于0x7FF表示的就是类
    var Kind: InProcess
    // 父类的Metadata,如果是null说明是最顶级的类了
    var Superclass: UnsafeMutablePointer<ClassMetadata>
    // 缓存数据用于某些动态查找,它由运行时拥有,通常需要与Objective-C的使用进行互操作。(说到底就是OC的东西)
    var CacheData1: UnsafeMutablePointer<UnsafeRawPointer>
    var CacheData2: UnsafeMutablePointer<UnsafeRawPointer>
    // 除了编译器设置低位以表明这是Swift元类型外,这个data里存的指针,用于行外元数据,通常是不透明的(应该也是OC的)
    var Data: InProcess  
    // Swift-specific class flags.
    var Flags: ClassFlags
    // The address point of instances of this type.
    var InstanceAddressPoint: UInt32
    // The required size of instances of this type.(实例对象在堆内存的大小)
    var InstanceSize: UInt32
    // The alignment mask of the address point of instances of this type. (根据这个mask来获取内存中的对齐大小)
    var InstanceAlignMask: UInt16
    // Reserved for runtime use.(预留给运行时使用)
    var Reserved: UInt16
    // The total size of the class object, including prefix and suffix extents.
    var ClassSize: UInt32
    // The offset of the address point within the class object.
    var ClassAddressPoint: UInt32
    // 一个对类型的超行的swift特定描述,如果这是一个人工子类,则为null。目前不提供动态创建非人工子类的机制。
    var Description: UnsafeMutablePointer<TargetClassDescriptor>
    // 销毁实例变量的函数,用于在构造函数早期返回后进行清理。如果为null,则不会执行清理操作,并且所有的ivars都必须是简单的。
    var IVarDestroyer: UnsafeMutablePointer<ClassIVarDestroyer>
}

对比StructMeteData,ClassMetadata多了很多属性。

// Swift中的class的metadata兼容OC的类
struct ClassMetadata {
    // 在oc中放的就是isa,在swift中kind大于0x7FF表示的就是类
    var Kind: InProcess
    // 父类的Metadata,如果是null说明是最顶级的类了
    var Superclass: UnsafeMutablePointer<ClassMetadata>
    // 缓存数据用于某些动态查找,它由运行时拥有,通常需要与Objective-C的使用进行互操作。(说到底就是OC的东西)
    var CacheData1: UnsafeMutablePointer<UnsafeRawPointer>
    var CacheData2: UnsafeMutablePointer<UnsafeRawPointer>
    // 除了编译器设置低位以表明这是Swift元类型外,这个data里存的指针,用于行外元数据,通常是不透明的(应该也是OC的)
    var Data: InProcess
    
    // 该对象是否是有效的swift类型元数据? 也就是说,它可以安全地向下转换到类元数据(ClassMetadata)吗?
    func isTypeMetadata() -> Bool {
        return ((Data & 2) != 0)
    }
    
    func isPureObjC() -> Bool {
        return !isTypeMetadata()
    }
    
    /**
     源码中
     上面的字段都是TargetAnyClassMetadata父类的,类元数据对象中与所有类兼容的部分,即使是非swift类。
     下面的字段都是TargetClassMetadata的,只有在isTypeMetadata()时才有效,所以在源码在使用时都比较小心,会经常调用isTypeMetadata()
     Objective-C运行时知道下面字段的偏移量
     */
    
    // Swift-specific class flags.
    var Flags: ClassFlags
    // The address point of instances of this type.
    var InstanceAddressPoint: UInt32
    // The required size of instances of this type.(实例对象在堆内存的大小)
    var InstanceSize: UInt32
    // The alignment mask of the address point of instances of this type. (根据这个mask来获取内存中的对齐大小)
    var InstanceAlignMask: UInt16
    // Reserved for runtime use.(预留给运行时使用)
    var Reserved: UInt16
    // The total size of the class object, including prefix and suffix extents.
    var ClassSize: UInt32
    // The offset of the address point within the class object.
    var ClassAddressPoint: UInt32
    // 一个对类型的超行的swift特定描述,如果这是一个人工子类,则为null。目前不提供动态创建非人工子类的机制。
    var Description: UnsafeMutablePointer<TargetClassDescriptor>
    // 销毁实例变量的函数,用于在构造函数早期返回后进行清理。如果为null,则不会执行清理操作,并且所有的ivars都必须是简单的。
    var IVarDestroyer: UnsafeMutablePointer<ClassIVarDestroyer>
    
    
    //获得每个属性的在结构体中内存的起始位置
    mutating func getFieldOffset(index: Int) -> Int {
        if Description.pointee.NumFields == 0 || Description.pointee.FieldOffsetVectorOffset == 0 {
            print("没有属性")
            return 0
        }
        let fieldOffsetVectorOffset = self.Description.pointee.FieldOffsetVectorOffset
        return withUnsafeMutablePointer(to: &self) {
            //获得自己本身的起始位置
            let selfPtr = UnsafeMutableRawPointer($0).assumingMemoryBound(to: InProcess.self)
            //以指针的步长偏移FieldOffsetVectorOffset
            let fieldOffsetVectorOffsetPtr = selfPtr.advanced(by: numericCast(fieldOffsetVectorOffset))
            //属性的起始偏移量已32位整形存储的,转一下指针
            let tramsformPtr = UnsafeMutableRawPointer(fieldOffsetVectorOffsetPtr).assumingMemoryBound(to: InProcess.self)
            return numericCast(tramsformPtr.advanced(by: index).pointee)
        }
    }
}

除了Struct有的一些属性外,多了一些适配OC的属性。当然存储的属性信息也是放在Description。Description中的FieldRecord和Struct的一样,我就不贴出来了。

struct TargetClassDescriptor {
    // 存储在任何上下文描述符的第一个公共标记
    var Flags: ContextDescriptorFlags

    // 复用的RelativeDirectPointer这个类型,其实并不是,但看下来原理一样
    // 父级上下文,如果是顶级上下文则为null。
    var Parent: RelativeDirectPointer<InProcess>

    // 获取类的名称
    var Name: RelativeDirectPointer<CChar>

    // 这里的函数类型是一个替身,需要调用getAccessFunction()拿到真正的函数指针(这里没有封装),会得到一个MetadataAccessFunction元数据访问函数的指针的包装器类,该函数提供operator()重载以使用正确的调用约定来调用它(可变长参数),意外发现命名重整会调用这边的方法(目前不太了解这块内容)。
    var AccessFunctionPtr: RelativeDirectPointer<UnsafeRawPointer>

    // 一个指向类型的字段描述符的指针(如果有的话)。类型字段的描述,可以从里面获取结构体的属性。
    var Fields: RelativeDirectPointer<FieldDescriptor>
    
    // The type of the superclass, expressed as a mangled type name that can refer to the generic arguments of the subclass type.
    var SuperclassType: RelativeDirectPointer<CChar>
    
    // 下面两个属性在源码中是union类型,所以取size大的类型作为属性(这里貌似一样),具体还得判断是否have a resilient superclass
    
    // 有resilient superclass,用ResilientMetadataBounds,表示对保存元数据扩展的缓存的引用
    var ResilientMetadataBounds: RelativeDirectPointer<TargetStoredClassMetadataBounds>
    // 没有resilient superclass使用MetadataNegativeSizeInWords,表示该类元数据对象的负大小(用字节表示)
    var MetadataNegativeSizeInWords: UInt32 {
        get {
            return UInt32(ResilientMetadataBounds.offset)
        }
    }

    // 有resilient superclass,用ExtraClassFlags,表示一个Objective-C弹性类存根的存在
    var ExtraClassFlags: ExtraClassDescriptorFlags
    // 没有resilient superclass使用MetadataPositiveSizeInWords,表示该类元数据对象的正大小(用字节表示)
    var MetadataPositiveSizeInWords: UInt32 {
        get {
            return ExtraClassFlags.Bits
        }
    }
    /**
     此类添加到类元数据的其他成员的数目。默认情况下,这些数据对运行时是不透明的,而不是在其他成员中公开;它实际上只是NumImmediateMembers * sizeof(void*)字节的数据。
     这些字节是添加在地址点之前还是之后,取决于areImmediateMembersNegative()方法。
     */
    var NumImmediateMembers: UInt32
    
    // 属性个数,不包含父类的
    var NumFields: Int32
    // 存储这个结构的字段偏移向量的偏移量(记录你属性起始位置的开始的一个相对于metadata的偏移量,具体看metadata的getFieldOffsets方法),如果为0,说明你没有属性
    // 如果这个类含有一个弹性的父类,那么从他的弹性父类的metaData开始偏移
    var FieldOffsetVectorOffset: Int32
}

还有一点需要注意的是,ClassMetadata是在堆中的数据,类似OC中的元数据。如刚开始所比对的,结构体和类实例在内存地址的不同表现。类对象在栈中只是一个指针,数据存储在堆中,而ClassMetadata就是堆中数据头部的指针,所谓的指向类型信息。

所以以下我们通过创建对象的源码来探索对象实例的本质。swift_allocObject的源码如下:

// requiredSize是分配的实际内存大小,为40
// requiredAlignmentMask是swift中的字节对齐方式,这个和OC中是一样的,必须是8的倍数,不足的会自动补齐,目的是以空间换时间,来提高内存操作效率。
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  assert(isAlignmentMask(requiredAlignmentMask));
  // 通过swift_slowAlloc分配内存,并进行内存字节对齐
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask));//分配内存+字节对齐

  // NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
  // check on the placement new allocator which we have observed on Windows,
  // Linux, and macOS.
  new (object) HeapObject(metadata);//初始化一个实例对象

  // If leak tracking is enabled, start tracking this object.
  SWIFT_LEAKS_START_TRACKING_OBJECT(object);

  SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);

  return object;
}

通过new + HeapObject + metadata初始化一个实例对象。函数的返回值是HeapObject类型,所以当前对象的内存结构就是HeapObject的内存结构。
进入swift_slowAlloc函数,其内部主要是通过malloc在堆中分配size大小的内存空间,并返回内存地址,主要是用于存储实例变量。

void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
  void *p;
  // This check also forces "default" alignment to use 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;
}

通过查看HeapObject初始化方法,我们可以看到需要两个参数:metadata、refCounts。其中metadata类型是HeapMetadata,是一个指针类型,占8字节,后面我会讲它怎么和ClassMetadata扯上关系。
refCounts(引用计数,类型是InlineRefCounts,而InlineRefCounts是一个类RefCounts的别名,占8个字节),swift采用arc引用计数。

所以总结起来,实例对象在OC和Swift的区别是:

  • OC中实例对象的本质是结构体,是以objc_object为模板继承的,其中有一个isa指针,占8字节。
  • Swift中实例对象,本质也是结构体(HeapObject),默认的比OC中多了一个refCounted引用计数大小,默认属性占16字节(metadata 8字节 + refCounts 8字节)。

再接着上面的研究HeapMetadata和ClassMetadata的关系。
HeapMetadata中有个Kind,在getClassObject中去匹配kind返回值是TargetClassMetadata类型。如果是Class,则直接对this(当前指针,即metadata)强转为ClassMetadata。

 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;
    }
  }

参考资料或博客
1.《Swift中进阶合集》
2.《初探Swift底层Metadata》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容