该篇主要是关于研究Class和Struct的底层结构,以及Swift反射的相关知识。
1.Struct和Class的内存分布
class Size {
var width = 1
var height = 2
}
struct Point {
var x = 3
var y = 4
}
众所周知,结构体的值是直接储存在栈空间,类的值是储存在堆空间,栈空间是堆的地址指针。
那我们所研究的底层结构又是什么?储存在哪里?
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存储着很多信息(基本包括所有我们想要的信息)。
类型名称,属性数量,属性偏移量这些都是比较常用的,我们重点讲解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获取的属性偏移地址获取的。
3. Struct的底层结构
在上面Mirror的解析中,已经对Struct的底层结构有了进一步研究。
很明显,再概括性的解释下StructMetaData,Struct的底层结构就清晰可见了。
如上一部分Mirror解析所说的,Mirror底层的核心逻辑,就是通过getKind()方法获取该类型的元数据类型,然后根据该类型Metadata获取相应的属性,比如类型名称、属性名字,属性个数等。
每种类型都有对应的Metadata,所以研究Struct或者Class都是通过对应的Metadata来研究的。
通过对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的相关属性。
// 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》