Swift类和结构体(一)

322 阅读5分钟

一、首先我们直接来说Swift里类与结构体的关系

主要相同点:

- 定义存储值的属性
- 定义方法
- 定义下标以使用下标语法提供对其值的访问
- 定义初始化器
- 使用 extension 来拓展功能
- 遵循协议来提供某种功能

主要不同点:

- 有继承的特性,而结构体没有
- 类型转换使您能够在运行时检查和解释类实例的类型
- 类有析构函数用来释放其分配的资源
- 引用计数允许对一个类实例有多个引用 

对于类和结构体来说,我们首先需要区分的就是: 类是引用类型,而结构体是值类型

类引用参考

image.png

结构体值参考

    image.png

这里我们能通过几个LLDB命令行指令查看内存结构

po : p  po 的区别在于使用 po 只会输出对应的值,而 p 则会返回值的类型以及命令结果 的引用名。 

x/8g: 读取内存中的值(8g: 8字节格式输出) 

例如

-类:

class Teacher {
    var name: String
    init(_ name: String) {
    self.name = name
    }
} 
var t = Teacher()

image.png

-结构体

struct Student {
    var name: String
}
var s = Student(name: "Li")

image.png

二、类和结构体的初始化

需要注意的是 在类里,编译器默认不会为我们自动初始化成员变量,固当我们定义了成员变量时,我们要手动创建初始化器(指定初始化器),否则编译器会报错。例如上面的例子中我们在Teacher类中 定义了init(_ name: String) 初始化方法。

1、便捷初始化器

同时,我们也能为当前类提供便捷初始化器(注意:便捷初始化器必须从相同的类里调用另外一个初始化器)。

我们修改上面的Teacher类

class Teacher {
    var name: String
    var age: Int
    init(_ age: Int, _ name: String) {
        self.name = name
        self.age = age
    }
    
    convenience init() {
        self.init(20, "Li")
    }
}
var t = Teacher()

对于便捷初始化器我们需要记住几点规则:

  • 指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成。
  • 指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖
  • 便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括 同类里定义的属性)。如果没这么做,便捷构初始化器赋予的新值将被自己类中其它指定初始化器所覆盖
  • 初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例 属性的值,也不能引用 self 作为值 

2、可失败初始化器

这个很好理解,就是在初始化时,存在返回nil 的初始化器。在有些类里,我们可能定义了一些判断条件,若初始化时,条件不符合则返回初始化失败即nil。

例如:

class Teacher {
    var name: String
    var age: Int
    init?(_ age: Int, _ name: String) {
        guard age >= 20 else {return nil}
        self.name = name
        self.age = age
    }
    convenience init?() {
        self.init(18, "Li")
    }
}

var t = Teacher()

此时,t 这个实例就是nil  因为在便捷初始化器里默认是age = 18 是小于 20的,固初始化失败

三、类的生命周期

1、编译

在iOS开发中,不管是Objective-C开发,还是 Swift开发编译器最终都是使用LLVM进行编译的,如图:

image.png

 

Objective-C 是通过clang 编译器编译成IR,然后再生成可执行文件.O的(即机器码)

Swift 则通过Swift编译器编译成IR。然后生成可执行文件。其流程如下

// 分析输出AST
swiftc main.swift -dump-parse

// 分析并且检查类型输出AST
swiftc main.swift -dump-ast

// 生成中间体语言(SIL),未优化
swiftc main.swift -emit-silgen

// 生成中间体语言(SIL),优化后的
swiftc main.swift -emit-sil

// 生成LLVM中间体语言 (.ll文件)
swiftc main.swift -emit-ir

// 生成LLVM中间体语言 (.bc文件)
swiftc main.swift -emit-bc

// 生成汇编
swiftc main.swift -emit-assembly

// 编译生成可执行.out文件
swiftc -o main.o main.swift

2、Swift内存分配

在 Swift 对象分配中时,主要是通过以下流程创建对象

     __allocating_init —> swift_allocObject —>  _swift_allocObject_  —> swift_slowAlloc —> Malloc 

Swift 对象的内存结构是 HeapObject(堆) 而OC是 isa 指针 heapObject 里有两个属性 Metadata 和 RefCount, 默认大小是16字节

我们通过阅读源码swift-swift-5.5.2-RELEASE 可以知道 TargetMetadata 是基类,为了继续深究 swift 类的数据结构,继续分析 TargetMetadata 类 通过 getTypeContextDescriptor() 函数 我满看到在判断是 class 时,会把 this 指针强转为 TargetClassMetadata 类型

image.png

我们再到 struct TargetClassMetadata 中看到

image.png

通过整理我们得出

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
}

这样的结构。

下面我们通过代码来证明一下我们的推论是否正确。

//Metadata 结构体
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
struct HeapObject {
    // 指针
    var metadate: UnsafeRawPointer
    var refcounted1: uint32
    var refcounted2: uint32
}

class Teacher {
  var age: Int = 20
  var name: String = "Li"
}
var t = Teacher()

// 获取实例对象的指针
let objcPoint = Unmanaged.passUnretained(t as AnyObject).toOpaque()

// 将 objcPoint 绑定成 HeapObject 类型
let objcHeapPoint = objcPoint.bindMemory(to: HeapObject.self, capacity: 1)

// objcHeapPoint.pointee 获取指针,swift 固定语法
print(objcHeapPoint.pointee)


// 将 objcHeapPoint.pointee.metadate 的指针绑定为 Metadata.self 类型,MemoryLayout 用来测量数据类型的大小
let metadata = objcHeapPoint.pointee.metadate.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride).pointee

print(metadata)

运行起来后 打印出

image.png

固我们以上的 Metadate 结构体推论是正确的。