Swift类和结构体
在面向对象的开发过程中,会将所有对象都抽象成类或者结构体。本文将通过类比,来总结在swift语言中,类和结构体的异同。
认识类与结构体(看似栾生兄弟)
class OSRoom {
var roomId: String
var roomName: String
init(_ roomId: String, _ roomName: String) {
self.roomId = roomId
self.roomName = roomName
}
}
var room = OSRoom("001", "卧室")
struct OSBed {
var bedId: String
var bedWidth: Float
init(_ bedId: String, _ bedWidth:Float) {
self.bedId = bedId
self.bedWidth = bedWidth
}
}
var bed = OSBed("001", 1.8)
咋一看,类和结构体在定义的时候非常相似,只是定义关键字不同。实则两者有很多不同。例如,结构体在定义的时候可以不定义构造器(构造函数),编译器会自动生成。而类在定义的时候编译器不会自动生成构造器。
类与结构体的区别(实则性格各异)
结构体和类的主要相同点有:
- 定义存储值的属性
- 定义方法
- 定义下标以使用下标语法提供对其值的访问
- 定义初始化器
- 使用extension来拓展功能
- 遵循协议来提供某种功能
主要的不同点有:
- 类有继承的特性,而结构体没有
- 类型转换使您能够在运行时检查和解释类实例的类型
- 类有析构函数用来释放其分配的资源
- 引用计数允许对一个类实例有多个引用
类型不同(同父异母)
对于类与结构体我们需要区分的第一件事就是类是引用类型,而结构体是值类型。即在赋值的过程中,类赋值的是地址,而结构体赋值的是值。这点可以通过代码 来证明。
func testStruchAndClass() {
var room = OSRoom.init("001", "客厅")
var room2 = room
room2.roomId = "002"
var bed = OSBed.init("001", 2.0)
var bed1 = bed
bed.bedId = "002";
print(room, room2)
print(bed, bed1)
print(room.roomId, room2.roomId, bed.bedId, bed1.bedId)
输出
ObjectStudy.OSRoom ObjectStudy.OSRoom
OSBed(bedId: "002", bedWidth: 2.0) OSBed(bedId: "001", bedWidth: 2.0)
002 002 002 001
而在类型不同的背后,他们的在内存的存储位置也有所不同。我们知道栈区(stack)存储局部变量和函数运行过程中的上下文,而堆取(Heap)存储所有对象。在程序进行运算的过程中。如果涉及到对象,会在栈区找到指针然后再去堆区访问对象本身,而在访问结构体的时候可以直接在栈区访问结构体本身。(跟堆栈也有关系)所以在访问效率上也有所不同。
这里我们也可以通过github上StructVsClassPerformance这个案例来直观的测试当前结构体和类的时间分配。
Swift编译过程
在OC开发中,前端使用Clang编译。而在swift中前端使用swiftc编译。后端都是通过LLVM进行编译的。如下图所示:
- swiftc常用命令
-dump-ast 解析和类型检查源文件 & 转换成 AST
-dump-parse 解析源文件 & 转换成 AST
-emit-assembly 生成汇编文件
-emit-bc 生成 LLVM Bitcode 文件
-emit-executable 生成已链接的可执行文件
-emit-imported-modules 生成已导入的库
-emit-ir 生成 LLVM IR 文件
-emit-library 生成已连接的库
-emit-object 生成目标文件
-emit-silgen 生成 raw SIL 文件(第一个阶段)
-emit-sil 生成 canonical SIL 文件(第2个阶段)
-index-file 为源文件生成索引数据
-print-ast 解析和类型检查源文件 & 转换成更简约的格式更好的 AST
-typecheck 解析和类型检查源文件
- swift编译过程
- Swift Code 经过 -dump-parse 进行语义分析解析成抽象语法树(Parse)
- Parse 经过 -dump-ast 进行语义分析语法是否正确,是否安全。
- Seam 之后会把 Swift Code 会降级变成 SILGen(Swift 中间代码),对于 SILGen 又分为原生的(Raw SIL)和经过优化的(SIL Opt Canonical SIL)。
- 优化完成的 SIL 会由 LLVM 降级成为 IR,降级成 IR之后由后端代码编译成机器码。
如图:
我们可以通过swift的中SIL(swift中间语言)来观察swift中创建一个对象的过程。
探索类初始化过程
我们结合汇编调试、Swift源码、以及SIL可以追踪对象创建的过程。
在新建对象的时候打断点,并开启汇编调试
class OSRoom {
var roomId: String
var roomName: String
init(_ roomId: String, _ roomName: String) {
self.roomId = roomId
self.roomName = roomName
}
}
func initRoom() {
let room = OSRoom("003", "阳台")
print(room)
}
跟踪断点会发现执行了 __allocating_init 继续点进这个方法内部(按住control建,点击进入断点按钮⬇️)。
进去之后会发现有2个方法,swift_allocObject 和OSRoom.init方法。不难猜出,swift_allocObject是申请空间,init是初始化操作。
接下来想继续了解swift_allocObject中的具体操作,需要借助swift源码
下载源码后,打开,全局搜索swift_allocObject
在 HeapObject.cpp 文件中找到 swift_allocObject 函数的实现,并且在 swift_allocObject 函数的实现上方,有一个 _swift_allocObject_ 函数的实现。
// 第一个参数,元数据。
// 第二个参数,分配内存的大小
// 第三个参数,内存对齐,值一般为 7,因为遵守8字节对齐
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));
// 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;
}
在函数的内部会调用一个 swift_slowAlloc 函数,我们来看下 swift_slowAlloc 函数的内部实现:
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);
#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;
}
走到这,已经证明了之前的猜想,这里边调用了malloc方法。
目前可以总结Swift创建一个对象的流程如下:
__allocating_init ----->swift_allocObject-----> _swift_allocObject_ -----> swift_slowAlloc -----> Malloc
类的结构
我们都知道OC中类的结构是objc_class
大胆猜想
我们可以通过看源码,来对swift中的类对象的结构进行猜测。 来到HeapObject.h中。
SWIFT_RUNTIME_EXPORT
HeapObject *swift_allocObject(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask);
\
/// Initializes the object header of a stack allocated object.
///
/// \param metadata - the object's metadata which is stored in the header
/// \param object - the pointer to the object's memory on the stack
/// \returns the passed object pointer.
SWIFT_RUNTIME_EXPORT
HeapObject *swift_initStackObject(HeapMetadata const *metadata,
HeapObject *object);
\
/// Initializes the object header of a static object which is statically
/// allocated in the data section.
///
/// \param metadata - the object's metadata which is stored in the header
/// \param object - the address of the object in the data section. It is assumed
/// that at offset -1 there is a swift_once token allocated.
/// \returns the passed object pointer.
SWIFT_RUNTIME_EXPORT
HeapObject *swift_initStaticObject(HeapMetadata const *metadata,
HeapObject *object);
可以看到swift_allocObject、swift_initStackObject的返回值是一个HeapObject,我们可以大胆猜想这个HeapObject是swift中所有对象的的祖先类。在Object-C中NSObject中有个isa指针,该指针指实例对象的结构类对象。那这个HeapObject里面会不会有isa指针呢?
// 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 *__ptrauth_objc_isa_pointer metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
}
并没发现isa指针,但是看到了HeapMetadata,看注释这是个有效的指针对于一个metadata object那这个metadata应该是跟isa指针是同样的功能吧。
using HeapMetadata = TargetHeapMetadata<InProcess>;
再看看TargetHeapMetadata的结构
struct TargetHeapMetadata : TargetMetadata<Runtime> {
using HeaderType = TargetHeapMetadataHeader<Runtime>;
//初始化
TargetHeapMetadata() = default;
constexpr TargetHeapMetadata(MetadataKind kind)
: TargetMetadata<Runtime>(kind) {}
//如果和OC交互
#if SWIFT_OBJC_INTEROP
constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
: TargetMetadata<Runtime>(isa) {}
#endif
};
可以看到如果和OC交互,它吧这个转换成了isa,说明这个极有可能是swift结构类。看它的父类可以大致总结其结构
struct Metadata{
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}
小心论证
将HeapObject和Metadate定义结构体,定义一个类,注意不要继承NSObject.
struct HeapObject{
var metadate: UnsafeRawPointer
//代表1个64位的refCount
var refCount1: UInt32
var refCount2: UInt32
}
struct Metadata{
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}
class OSClassStructureModel {
var modelId: UInt = 0x0
}
初始化实例对象,将势力对象类型转换成HeapObject类型,访问其metadate属性,并转换成Metadata并将其输出。
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
let model1 = OSClassStructureModel()
//获取实力对象的指针
let objectRowPtr = Unmanaged.passUnretained(model1 as AnyObject).toOpaque()
//绑定指针(类型转化)
let objPtr = objectRowPtr.bindMemory(to: HeapObject.self, capacity: 1)
//访问指针
print(objPtr.pointee)
let mateDate = objPtr.pointee.metadate.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride).pointee
print(mateDate)
}
可以看到superClass中已经有
SwiftObject,这也证实了我们之前的猜想。