与OC对比:
-
实例对象 & 类
-
OC
中的实例对象本质是结构体,是通过底层的objc_object
模板创建,类是继承自objc_class
-
Swift中
的实例对象本质也是结构体,类型是HeapObject
,比OC多了一个refCounts
-
-
方法列表
-
OC
中的方法存储在objc_class
结构体class_rw_t
的methodList
中 -
swift
中的方法存储在metadata元数据中
-
-
引用计数
-
OC
中的ARC
维护的是散列表 -
swift
中的ARC
是对象内部有一个refCount
属性
-
Swift属性
在swift中, 属性主要分为存储属性、计算属性、延迟存储属性、类型属性
存储属性
存储属性:常量存储属性(let修饰)、变量存储属性(var修饰)
代码如下
class Teacher {
let age: Int = 18
var name: String = "Hello"
}
var t = Teacher()
其中代码中的age
、name
都是变量存储属性
,在SIL
中可以看出
class Teacher {
//_hasStorage 表示是存储属性
@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue var name: String { get set }
@objc deinit
init()
}
存储属性的特征:会占用分配内存实例的内存空间
- po t
- x/8g 内存地址,即HeapObject的内存地址
计算属性
计算属性:不占用内存空间,本质是set/get
方法的属性
下面先看一个demo,这demo有问题吗?
class Teacher {
var age: Int {
get {
return 18
}
set {
age = newValue
}
}
}
然后运行发现崩溃了,原因是age
的set
方法中调用age.set
导致了循环引用,即递归
验证不占用内存
class Square{
var width: Double = 8.0
var area: Double{
get{
//这里的return可以省略,编译器会自动推导
return width * width
}
set{
width = sqrt(newValue)
}
}
}
print(class_getInstanceSize(Square.self))
//打印结果:
24
从结果可以看出类Square
的内存大小是24,等于 (metadata
+ refCounts
)类自带16字节 + width
(8字节) = 24,是没有加上area
的。从这里可以证明area
属性没有占有内存空间。
验证:本质是set/get方法
- 将
main.swift
转换为SIL
文件:swiftc -emit-sil main.swift >> ./main.sil
- 查看
SIL
文件,对于存储属性,有_hasStorage的
标识符
class Square {
@_hasStorage @_hasInitialValue var width: Double { get set }
var area: Double { get set }
@objc deinit
init()
}
- 对于计算属性,
SIL
中只有setter、getter
方法
属性观察者(didSet、willSet)
-
willSet
:新值存储之前调用newValue
-
didSet
:新值存储之后调用oldValue
class Teacher{
var name: String = "测试"{
//新值存储之前调用
willSet{
print("willSet newValue \(newValue)")
}
//新值存储之后调用
didSet{
print("didSet oldValue \(oldValue)")
}
}
}
var t = CJLTeacher()
t.name = "swift"
//**********打印结果*********
willSet newValue swift
didSet oldValue 测试
通过以上代码可以验证观察者属性
那么以下几个问题思考一下
问题一:init方法中是否会触发属性观察者?
class Teacher{
var name: String = "测试"{
//新值存储之前调用
willSet{
print("willSet newValue \(newValue)")
}
//新值存储之后调用
didSet{
print("didSet oldValue \(oldValue)")
}
}
init() {
self.name = "swift"
}
}
运行后结果发现,并没有走willSet、didSet中的打印方法,所以有以下结论:
- 在
init
方法中,如果调用属性,是不会触发属性观察者的 -
init
中主要是初始化当前变量,除了默认的前16
个字节,其他属性会调用memset
清理内存空间(因为有可能是脏数据,即被别人用过),然后才会赋值
【总结】:初始化器(即init
方法设置)和定义时设置默认值(即在didSet
中调用其他属性值)都不会触发
问题二:哪里可以添加属性观察者?
-
类
中定义的存储属性
- 通过
类
继承的存储属性
class MediumTeacher: Teacher{
override var age: Int{
//新值存储之前调用
willSet{
print("willSet newValue \(newValue)")
}
//新值存储之后调用
didSet{
print("didSet oldValue \(oldValue)")
}
}
}
- 通过
类
继承的计算属性
class Teacher{
var age: Int = 18
var age2: Int {
get{
return age
}
set{
self.age = newValue
}
}
}
var t = Teacher()
class MediumTeacher: Teacher{
override var age: Int{
//新值存储之前调用
willSet{
print("willSet newValue \(newValue)")
}
//新值存储之后调用
didSet{
print("didSet oldValue \(oldValue)")
}
}
override var age2: Int{
//新值存储之前调用
willSet{
print("willSet newValue \(newValue)")
}
//新值存储之后调用
didSet{
print("didSet oldValue \(oldValue)")
}
}
}
问题三:子类和父类的存储属性同时存在didset、willset时,其调用顺序是什么?
class Teacher{
var age: Int = 18{
//新值存储之前调用
willSet{
print("父类 willSet newValue \(newValue)")
}
//新值存储之后调用
didSet{
print("父类 didSet oldValue \(oldValue)")
}
}
var age2: Int {
get{
return age
}
set{
self.age = newValue
}
}
}
class MediumTeacher: Teacher{
override var age: Int{
//新值存储之前调用
willSet{
print("子类 willSet newValue \(newValue)")
}
//新值存储之后调用
didSet{
print("子类 didSet oldValue \(oldValue)")
}
}
}
var t = MediumTeacher()
t.age = 20
打印结果:
子类 willSet newValue 20
父类 willSet newValue 20
父类 didSet oldValue 18
子类 didSet oldValue 18
结论:对于同一个属性,子类和父类都有属性观察者,其顺序是:先子类willset,后父类willset,在父类didset, 子类的didset,即:子父 父子
问题四:子类调用了父类的init,是否会触发观察属性?
class MediumTeacher: Teacher{
override var age: Int{
//新值存储之前调用
willSet{
print("子类 willSet newValue \(newValue)")
}
//新值存储之后调用
didSet{
print("子类 didSet oldValue \(oldValue)")
}
}
override init() {
super.init()
self.age = 20
}
}
打印结果:
子类 willSet newValue 20
父类 willSet newValue 20
父类 didSet oldValue 18
子类 didSet oldValue 18
从打印结果发现,会触发属性观察者,主要是因为子类调用了父类的init
,已经初始化过了,而初始化流程保证了所有属性都有值(即super.init
确保变量初始化完成了),所以可以观察属性了
延迟属性
- ?关键字 lazy 来标识?个延迟存储属性。
- 延迟存储属性的初始值在其第?次使?时才进?计算。
- 延迟存储属性并不能保证线程安全
- 延迟存储属性对实例对象大小的影响
class Teacher{
lazy var age: Int = 18
}
延迟存储在第一次访问的时候才被赋值
通过代码调试来查看一下内存变化情况
-
age
第一次访问前的内存情况:此时的age
是没值的,为0x0
-
age
第一次访问后的内存情况:此时age
是有值的,为20
因此可以验证懒加载存储属性只有在第一次访问时才会被赋值。
lazy修饰的属性有以下两点说明 - 1、
lazy
修饰的属性,在底层默认是optional
,在没有被访问时,默认是nil
,在内存中的表现就是0x0。在第一次访问过程中,调用的是属性的getter
方法,其内部实现是通过当前enum
的分支,来进行一个赋值操作。 - 2、可选类型是16字节吗?可以通过
MemoryLayout
打印
print(MemoryLayout<Optional<Int>>.stride)
print(MemoryLayout<Optional<Int>>.size)
打印结果:
16
9
为什么实际大小是9?Optional
其本质是一个enum
,其中Int
占8字节,另一个字节主要用于存储case
值
延迟存储属性并不能保证线程安全
延迟存储属性对实例对象大小的影响
-
不使用
lazy
修饰,类的内存大小是24
-
使用
lazy
修饰,类的内存大小是32
从而可以证明,使用lazy和不使用lazy,其实例对象的内存大小是不一样的
类型属性
- 使用
static
修饰,且是一个全局变量 - 类型属性必须有一个
默认初始值
- 类型属性只被
初始化一次
class Teacher{
static var age: Int = 18
}
// **** 使用 ****
var age = Teacher.age
单例的创建
class Teacher {
//1、使用 static + let 创建声明一个实例对象
static let shareInstance = Teacher()
//2、给当前init添加private访问权限
private init(){}
}
//使用(只能通过单例,不能通过init)
var t = Teacher.shareInstance