类和结构体的比较
swift
中的类:
class LLPerson {
var age: Int
var name: String
init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
}
var person = LLPerson(18, "LL")
var person1 = person
类是引用类型,person
到person1
是指针的赋值,指针指向的内容是一致的。
po
:p
和po
的区别在于使用po
只会输出对应的值,而p
则会返回值的类型以及命令结果的引用名。
x/8g
: 读取内存中的值(8g
:8
字节格式输出)
打印person
和person1
的地址是不一致的,存储的内存地址8
字节。
创建person
对象时,在栈区(stack
)分配8
字节,然后在堆区(heap
)寻找位置分配空间,将值放到堆区,指针赋值给person
,指针指向的是heap
。可以使用cat address xxx
查看指向。
swift
中的结构体:
struct LLPerson {
var age: Int
var name: String
init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
}
var person = LLPerson(18, "LL")
var person1 = person
person1.age = 19
person1.name = "GG"
结构体是值类型,po
打印出来的值,修改person1
中的值,不会影响person
。
对于类,需要通过指针去heap
中查找值,结构体只需要在stack
上移动指针就可以。
结构体和类的主要相同点:
- 定义存储值的属性
- 定义方法
- 定义下标以使用下标语法提供对其值的访问
- 定义初始化器
- 使用
extension
来拓展功能 - 遵循协议来提供某种功能
主要不同点:
- 类有继承的特性,而结构体没有
- 类型转换使您能够在运行时检查和解释类实例的类型
- 类有析构函数用来释放其分配的资源
- 引用计数允许对一个类实例有多个引用
可以通过github上StructVsClassPerformance这个案例来直观的测试当前结构体和类的时间分配。
类的初始化器
当前的类编译器默认不会自动提供成员初始化器,但是对于结构体来说编译器会提供默认的初始化方法(前提是我们自己没有指定初始化器)!
class LLPerson {
var age: Int
var name: String
init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
convenience init() {
self.init(18, "LL")
}
}
Swift
中创建类和结构体的实例时必须为所有的存储属性设置一个合适的初始值。所以类LLPerson
必须要提供对应的指定初始化器,同时我们也可以为当前的类提供便捷初始化器(convenience
)(注意:便捷初始化器必须从相同的类里调用另一个初始化器)。
便携初始化器必须先调用其他初始化器,否则会编译不通过报错。必须在访问属性时,初始化属性。为了安全考虑。
初始化器规则:
- 指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成。
- 指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖。
- 便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括同类里定义的属性)。如果没这么做,便捷构初始化器赋予的新值将被自己类中其它指定初始化器所覆盖。
- 初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用
self
作为值。
可失败初始化器:
class LLPerson {
var age: Int
var name: String
init?(_ age: Int, _ name: String) {
if age < 18 {return nil}
self.age = age
self.name = name
}
convenience init?(_ age: Int) {
self.init(18, "LL")
}
}
这种 Swift
中可失败初始化器写 return nil
语句,来表明可失败初始化器在何种情况下会触发初始化失败。
必要初始化器:
在类的初始化器前添加 required
修饰符来表明所有该类的子类都必须实现该初始化器。
类的生命周期
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
可以通过swiftc -emit-sil ${SRCROOT}/LLSwiftTest/main.swift > ./main.sil & open main.sil
生成si
l文件:
class LLPerson {
var age: Int = 18
var name: String = "LL"
}
class LLPerson {
@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue var name: String { get set }
@objc deinit
init()
}
SLL
文件可以参考https://github.com/apple/swift/blob/main/docs/SLL.rst。
Swift
对象内存分配:
__allocating_init -----> swift_allocObject -----> swift_allocObject -----> swift_slowAlloc -----> Malloc
Swift
对象的内存结构 HeapObject (OC objc_object)
,有两个属性: 一个是Metadata
,一个是 RefCount
,默认占用 16
字节大小。
经过访问源码可得出以下结构:
类结构:
struct HeapObject { //自定义类结构
var metaData: UnsafeRawPointer
var refcounted1: UInt32
var refcounted2: UInt32
}
class LLPerson {
var age: Int = 18
var name: String = "LL"
}
var person = LLPerson()
let objcRawPtr = Unmanaged.passUnretained(person as AnyObject).toOpaque() //获取对象指针
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self, capacity: 1) //重新绑定
print(objcPtr.pointee)
metadata结构:
struct Metadata { //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
}
let metadata = objcPtr.pointee.metaData.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride).pointee
print(metadata)