Swift学习(一)类和结构体

638 阅读8分钟

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进行编译的。如下图所示:

4cd31fe1b5684feca3f5c3cd0cfcbbf3~tplv-k3u1fbpfcp-watermark.image.png

  • 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编译过程
  1. Swift Code 经过 -dump-parse 进行语义分析解析成抽象语法树(Parse)
  2. Parse 经过 -dump-ast 进行语义分析语法是否正确,是否安全。
  3. Seam 之后会把 Swift Code 会降级变成 SILGen(Swift 中间代码),对于 SILGen 又分为原生的(Raw SIL)和经过优化的(SIL Opt Canonical SIL)。
  4. 优化完成的 SIL 会由 LLVM 降级成为 IR,降级成 IR之后由后端代码编译成机器码。

如图:

6cee0ed3d6a54a699bf8b24e54e04fa8~tplv-k3u1fbpfcp-watermark.image.png

42d16a9c446743968657827f53f51fc1~tplv-k3u1fbpfcp-watermark.image.png

我们可以通过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)

    }

WX20211228-182059@2x.png

WX20211228-181906@2x.png

跟踪断点会发现执行了 __allocating_init 继续点进这个方法内部(按住control建,点击进入断点按钮⬇️)。

WX20211228-181555@2x.png

进去之后会发现有2个方法,swift_allocObject 和OSRoom.init方法。不难猜出,swift_allocObject是申请空间,init是初始化操作。

WX20211228-181656@2x.png

接下来想继续了解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

WX20220103-190409@2x.png

大胆猜想

我们可以通过看源码,来对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_allocObjectswift_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)

    }

WX20220104-095240@2x.png 可以看到superClass中已经有SwiftObject,这也证实了我们之前的猜想。