重学Swift第一篇:类结构探索

前言

内容主要是通过对Swift源码和SIL代码来学习Swift中底层的实现。

一、Swift编译过程

一个swift文件的编译过程如下:
在这里插入图片描述
Swift在编译过程中使用的LLVM前端编译器是swiftc,可通过swiftc -h查看了解各命令的作用。

二、SIL分析

在main.swift中定义Test类

class Test {}
var test = Test()

通过swiftc -emit-sil main.swift > main.sil && open -a Xcode main.sil把swift文件编译成sil文件并打开

class Test {  //Test类
  @objc deinit
  init()
}

@_hasStorage @_hasInitialValue var test: Test { get set }

// test  
//可通过xcrun swift-demangle s4main4testAA4TestCvp还原函数符号
sil_global hidden @$s4main4testAA4TestCvp : $Test

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main4testAA4TestCvp            // id: %2
  %3 = global_addr @$s4main4testAA4TestCvp : $*Test // user: %7
  %4 = metatype $@thick Test.Type                 // user: %6
  // function_ref Test.__allocating_init()
  %5 = function_ref @$s4main4TestCACycfC : $@convention(method) (@thick Test.Type) -> @owned Test // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thick Test.Type) -> @owned Test // user: %7
  store %6 to %3 : $*Test                         // id: %7
  %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'

// Test.__allocating_init()
sil hidden [exact_self_class] @$s4main4TestCACycfC : $@convention(method) (@thick Test.Type) -> @owned Test {
// %0 "$metatype"
bb0(%0 : $@thick Test.Type):
  %1 = alloc_ref $Test                            // user: %3
  // function_ref Test.init()
  %2 = function_ref @$s4main4TestCACycfc : $@convention(method) (@owned Test) -> @owned Test // user: %3
  %3 = apply %2(%1) : $@convention(method) (@owned Test) -> @owned Test // user: %4
  return %3 : $Test                               // id: %4
} // end sil function '$s4main4TestCACycfC'

// Test.init()
sil hidden @$s4main4TestCACycfc : $@convention(method) (@owned Test) -> @owned Test {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Test):
  debug_value %0 : $Test, let, name "self", argno 1 // id: %1
  return %0 : $Test                               // id: %2
} // end sil function '$s4main4TestCACycfc'

可以看到在main函数中调用了Test.__allocating_init(),在__allocating_init()中通过alloc_ref生成了Test的实例,接着调用init方法返回了自身。
可以在VSCode或Xcode中打一个__allocating_init符号断点,我这里源码是用VSCode打开的,所以在VSCode中打上断点运行后,单步调试进入了swift_allocObject函数

HeapObject *swift::swift_allocObject(HeapMetadata const *metadata,
                                     size_t requiredSize,
                                     size_t requiredAlignmentMask) {
  CALL_IMPL(swift_allocObject, (metadata, requiredSize, requiredAlignmentMask));
}

CALL_IMPL是一个宏,接着单步调试,进入_swift_allocObject_函数

static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask)); //分配内存,
  new (object) HeapObject(metadata); //创建HeapObject对象
  ......
  return object;
}

通过swift_slowAlloc方法分配了内存,然后通过new (object) HeapObject(metadata)在object的内存上就地创建HeapObject对象。

struct HeapObject {
  /// This is always a valid pointer to a metadata object.这始终是指向元数据对象的有效指针
  HeapMetadata const *metadata;   //指针8bytes
  InlineRefCounts refCounts;     //RefCounts类,类中有一个refCounts成员变量,类型为RefCountBitsT,类RefCountBitsT中只有一个uint64_t的bits成员变量,所以最后也占用8bytes
  ......
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }
};

可以看出对象是HeapObject *类型,它有两个属性,一个是metadata,一个是refCounts,默认占用16bytes。

三、类结构探索

从上面SIL分析中了解到类的结构体为HeapObject,HeapObject中metadata包含了元数据信息,refCounts用来表示引用计数。下面主要来看metadata,它的类型HeapMetadata源码如下:

using HeapMetadata = TargetHeapMetadata<InProcess>;

template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> { ... }

template <typename Runtime>
struct TargetMetadata {
	using StoredPointer = typename Runtime::StoredPointer;
	constexpr TargetMetadata() : Kind(static_cast<StoredPointer>(MetadataKind::Class)) {}
	......
	StoredPointer Kind; //InProcess::StoredPointer就是unsinged long long
	......
	const TargetClassMetadata<Runtime> *getClassObject() const;
}

template<> inline const ClassMetadata *
  Metadata::getClassObject() const {
    switch (getKind()) {
    case MetadataKind::Class: {
      // Native Swift class metadata is also the class object.
      return static_cast<const ClassMetadata *>(this);
    }
   	......
  }
}

可以看出HeapMetadata中没有属性,而它继承自TargetMetadata,而TargetMetadata有一个Kind属性。这个Kind属性可以标识元类数据的类型,具体如下
在这里插入图片描述
而TargetMetadata还有一个getClassObject()函数,可以根据kind拿到对应的metadata,接着来看ClassMetadata。

using ClassMetadata = TargetClassMetadata<InProcess>;

struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
  ClassFlags Flags; //swift类标记,这些标志仅在isTypeMetadata()时有效。
  uint32_t InstanceAddressPoint;  //实例地址点
  /// 'InstanceAddressPoint' bytes go before the address point;
  /// 'InstanceSize - InstanceAddressPoint' bytes go after it.
  uint32_t InstanceSize; //实例所需大小
  uint16_t InstanceAlignMask; //实例对齐掩码
  uint16_t Reserved;  //保留
  uint32_t ClassSize; //类对象的总大小,包括前缀和后缀范围。
  uint32_t ClassAddressPoint; //类对象内地址点的偏移量。
private:
  TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
public:
  TargetSignedPointer<Runtime, ClassIVarDestroyer * __ptrauth_swift_heap_object_destructor> IVarDestroyer;
}

template <typename Runtime>
struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
  ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata> Superclass;  //超类的元数据。根类为null。ObjC类没有元数据头
  TargetPointer<Runtime, void> CacheData[2]; //用于动态查找,它归运行时所有,通常需要与Objective-C的使用进行互操作。
  StoredSize Data; //unsigned long
}

综上可知ClassMetadata结构体可大致表示为

struct ClassMetadata {
  void* Kind;      //表示类型,相当于isa的功能
  void* Superclass;  //超类 
  void* CacheData1; //TargetPointer<Runtime, void> CacheData[2]是个数组,有两个元素,所以这里用CacheData1、CacheData2来表示
  void* CacheData2;
  void* Data;  //外联元数据, 用低位标志为Swift metatype,存在类型元数据头
  uint32_t Flags; //swift类标记,这些标志仅在isTypeMetadata()时有效。
  uint32_t InstanceAddressPoint;  //实例地址点
  uint32_t InstanceSize; //实例所需大小
  uint16_t InstanceAlignMask; //实例对齐掩码
  uint16_t Reserved;  //保留
  uint32_t ClassSize; //类对象的总大小,包括前缀和后缀范围。
  uint32_t ClassAddressPoint; //类对象内地址点的偏移量。
  void* Description;
  ......
}

类比objc中源码

struct swift_class_t : objc_class {
	//isa_t isa;
	//Class superclass;
	//cache_t cache;
	//class_data_bits_t bits;
    uint32_t flags;
    uint32_t instanceAddressOffset;
    uint32_t instanceSize;
    uint16_t instanceAlignMask;
    uint16_t reserved;

    uint32_t classSize;
    uint32_t classAddressOffset;
    void *description;
    // ...

    void *baseAddress() {
        return (void *)((uint8_t *)this - classAddressOffset);
    }
};

总结

  • 通过swiftc -emit-sil main.swift | xcrun swift-demangle > main.sil && open -a Xcode main.sil可以把swift源文件转为还原符号后的sil文件并用Xcode打开。
  • HeapObjec有一个HeapMetadata类型的metadata用来提供类的数据,通过metadata->getKind()可知元数据类型,通过metadata->getClassObject()可以拿到元数据。还有一个refCounts用来管理引用计数。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值