强引用
Swift使用ARC管理内存
OC创建实例对象,默认引用计数为0Swift创建实例对象,默认引用计数为1
class LGTeacher{
var age: Int = 18
var name: String = "Zang"
}
var t=LGTeacher()
var t1=t
var t2=t
上述代码,通过
LLDB指令来查看t的引⽤计数:
查看t的引⽤计数 输出的
refCounts为什么是0x0000000600000002?
通过源码进行分析,打开
HeapObhect.h,看到一个宏
HeapObhect.h
进入
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS宏定义,这里看到refCounts类型是InlineRefCounts
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS
进入
InlineRefCounts定义,它是RefCounts类型的别名
InlineRefCounts
进入
RefCounts定义,它是一个模板类。后续逻辑取决于模板参数RefCountBits,也就是上图中传入的InlineRefCountBits的类型
RefCounts
进入
InlineRefCountBits定义,它是RefCountBitsT类型的别名
InlineRefCountBits
首先确认
RefCountIsInline是什么,进入RefCountIsInline定义,本质上是enum,只有true和false。这里传入的RefCountIsInline就是true
RefCountIsInline
再进入到
RefCountBitsT的定义,里面的成员变量bits,类型为BitsType
RefCountBitsT
bits对RefCountBitsInt的Type属性取别名,本质上就是uint64_t类型
RefCountBitsInt
明白了
bits是什么,下面就来分析HeapObject的初始化方法,重点看第二个参数refCounts
HeapObject初始化方法
进入
Initialized定义,它的本质是一个enum,找到对应的refCounts方法,需要分析一下传入的RefCountBits(0, 1)到底在做什么
Initialized
进入
RefCountBits,还是模板定义,把代码继续往下拉...
RefCountBits
在下面找到真正的初始化方法
RefCountBitsT,传入strongExtraCount和unownerCount两个uint32_t类型参数,将这两个参数根据Offsets进行位移操作
RefCountBitsT
通过源码分析,最终我们得出这样⼀个
结论:
结论
isImmortal(0)UnownedRefCount(1-31):无主引用计数isDeinitingMask(32):是否进行释放操作StrongExtraRefCount(33-62):强引用计数UseSlowRC(63)
对照上述结论,使用二进制查看
refCounts输出的0x0000000600000002
二进制查看refCounts
1-31位是UnownedRefCount无主引用计数33-62位是StrongExtraRefCount强引用计数
通过SIL代码,分析
t的引用计数,当t赋值给t1、t2时,触发了copy_addr
SIL
查看SIL文档,
copy_addr内部又触发了strong_retain
copy_addr
回到源码,来到
strong_retain的定义,它其实就是swift_retain,其内部是一个宏定义CALL_IMPL,调用的是_swift_retain_,然后在_swift_retain_内部又调用了object->refCounts.increment(1)
strong_retain
进入
increment方法,里面的newbits是模板函数,其实就是64位整形。这里我们发现incrementStrongExtraRefCount方法点不进去,因为编译器不知道RefCountBits目前是什么类型
increment方法
我们需要回到
HeapObject,从InlineRefCounts进入,找到incrementStrongExtraRefCount方法
image.png 通过
BitsType方法将inc类型转换为uint64_t,通过Offsets偏移StrongExtraRefCountShift,等同于1<<33,十进制的1左移33位,再转换为十六进制,得到结果0x200000000。故此上述代码相当于bits += 0x200000000,左移33位后,在33-62位上,强引用计数+1
上述源码分析中,多次看到
C++的模板定义,其目是为了更好的抽象,实现代码重用机制的一种工具。它可以实现类型参数化,即把类型定义为参数, 从而实现真正的代码可重用性。模版可以分为两类,一个是函数模版,另外一个是类模版。
通过
CFGetRetainCount查看引用计数
class LGTeacher{
var age: Int = 18
var name: String = "Zang"
}
var t=LGTeacher()
print(CFGetRetainCount(t))
var t1=t
print(CFGetRetainCount(t))
var t2=t
print(CFGetRetainCount(t))
//输出以下内容:
//2
//3
//4
上述代码中,原本
t的引用计数为3,使用CFGetRetainCount方法会导致t的引用计数+1
弱引用
class LGTeacher{
var age: Int = 18
var name: String = "Zang"
var stu: LGStudent?
}
class LGStudent {
var age = 20
var teacher: LGTeacher?
}
func test(){
var t=LGTeacher()
weak var t1=t
print(CFGetRetainCount(t))
}
test()
//输出以下内容:
//2
上述代码,
t创建实例对象引用计数默认为1,使用CFGetRetainCount查看引用计数+1,打印结果为2。显然将t赋值给使用weak修饰的t1,并没有增加t的强引用计数
通过
LLDB指令来查看t的引⽤计数:
查看`t`的引⽤计数 将
t赋值给weak修饰的t1,查看refCounts打印出奇怪的地址
通过
LLDB指令来查看t1:
查看t1 使用
weak修饰的t1变成了Optional可选类型,因为当t被销毁时,t1会被置为nil,所以weak修饰的变量必须为可选类型
通过断点查看汇编代码,发现定义
weak变量,会调用swift_weakInit函数
查看汇编代码
通过源码进行分析,找到
swift_weakInit函数,这个函数由WeakReference调用,相当于weak字段在编译器声明过程中自定义了一个WeakReference对象,目的在于管理弱引用。在swift_weakInit函数内部调用了ref->nativeInit(value), 其中value就是HeapObject
swift_weakInit
进入
nativeInit方法,判断object不为空,调用formWeakReference
nativeInit
进入
formWeakReference方法,首先通过allocateSideTable方法创建SideTable,如果创建成功,调用incrementWeak
formWeakReference
进入
allocateSideTable方法,先通过refCounts拿到原有的引用计数,再通过getHeapObject创建SideTable,将地址传入InlineRefCountBits方法
allocateSideTable
进入
InlineRefCountBits方法,将参数SideTable的地址,直接进行偏移,然后存储到内存中,相当于将SideTable直接存储到uint64_t的变量中
InlineRefCountBits
之前查看
t的refCounts,打印出0xc0000000200d1d6e这串奇怪的地址,去掉62位和63位保留字段,剩余的就是偏移后的HeapObjectSideTableEntry实例对象的内存地址,即散列表的地址
二进制查看refCounts
回到源码分析,进入
HeapObjectSideTableEntry定义,里面有object对象和refCounts,refCounts是一个SideTableRefCounts类型
HeapObjectSideTableEntry
进入
SideTableRefCounts定义,它是RefCounts类型的别名,和之前分析的InlineRefCountBits类似,后续逻辑取决于模板参数的传入,这里传入的是SideTableRefCountBits类型
SideTableRefCounts
进入
SideTableRefCountBits定义,它继承于RefCountBitsT
SideTableRefCountBits
RefCountBitsT存储的是uint64_t类型的64位的信息,用于记录原有引用计数。除此之外SideTableRefCountBits自身还有一个uint32_t的weakBits,用于记录弱引用计数
还原散列表地址,查看弱引用
refCounts
- 将
0xc0000000200d1d6e地址62位和63位的保留字段清零,得到地址0x200D1D6E- 将
0x200D1D6E左移3位,还原成HeapObjectSideTableEntry对象地址0x10068EB70,也就是散列表地址- 通过
x/8g读取地址0x10068EB70
查看弱引用refCounts
循环引用
案例1:
闭包捕获外部变量
var age = 10
let clourse = {
age += 1
}
clourse()
print(age)
//输出以下内容:
//11
从输出结果来看, 闭包内部对变量的修改将会改变外部原始变量的值,因为闭包会捕获外部变量,这个与
OC中的block一致
案例2:
deinit反初始化器
class LGTeacher{
var age = 18
deinit{
print("LGTeacher deinit")
}
}
func test(){
var t = LGTeacher()
}
test()
//输出以下内容:
//LGTeacher deinit
当
test函数里的局部变量t被销毁时,会执行反初始化器deinit方法,这个与OC中的dealloc一致
案例3:
闭包修改实例变量的值,闭包能否对
t造成强引用?
class LGTeacher{
var age = 18
deinit{
print("LGTeacher deinit")
}
}
func test(){
var t = LGTeacher()
let closure = {
t.age += 1
}
closure()
print("age:\(t.age)")
}
test()
//输出以下内容:
//age:19
//LGTeacher deinit
从输出结果来看, 闭包对
t并没有造成强引用
案例4
将
案例3进行修改,在LGTeacher类里定义闭包类型属性completionBlock,在test函数内,调用t.completionBlock闭包,内部修改t.age属性,这样能否对t造成强引用?
class LGTeacher{
var age = 18
var completionBlock: (() ->())?
deinit{
print("LGTeacher deinit")
}
}
func test(){
var t = LGTeacher()
t.completionBlock = {
t.age += 1
}
print("age:\(t.age)")
}
test()
//输出以下内容:
//age:18
从输出结果来看,这里产生了循环引用,没有执行
deinit方法,也没有打印LGTeacher deinit。因为实例变量t的释放,需要等待completionBlock闭包的作用域释放,但闭包又被实例对象强引用,造成循环引用,t对象无法被释放
案例5
案例4中循环引用的两种解决方法
1、使用
weak修饰闭包传入的参数,参数的类型是Optional可选类型
func test(){
var t = LGTeacher()
t.completionBlock = { [weak t] in
t?.age += 1
}
print("age:\(t.age)")
}
//输出以下内容:
//age:18
//LGTeacher deinit
2、使用
unowned修饰闭包参数,与weak的区别在于unowned不允许被设置为nil,在运行期间假定它是有值的,所以使用unowned修饰要注意野指针的情况
func test(){
var t = LGTeacher()
t.completionBlock = { [unowned t] in
t.age += 1
}
print("age:\(t.age)")
}
//输出以下内容:
//age:18
//LGTeacher deinit
捕获列表
[unowned t]、[weak t]在Swift中叫做捕获列表
- 捕获列表的定义在参数列表之前
- 书写形式:⽤⽅括号括起来的表达式列表
- 如果使⽤捕获列表,即使省略参数名称、参数类型和返回类型,也必须使⽤
in关键字[weak t]就是获取t的弱引用对象,相当于OC中的weakself
var age = 0
var height = 0.0
let closure = { [age] in
print(age)
print(height)
}
age = 10
height = 1.85
closure()
//输出以下内容:
//0
//1.85
上述代码中,捕获列表的
age是常量,并且进行了值拷贝。对于捕获列表中的每个常量,闭包会利⽤周围范围内具有相同名称的常量或变量,来初始化捕获列表中定义的常量。

查看t的引⽤计数
输出的
HeapObhect.h
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS
InlineRefCounts
RefCounts
InlineRefCountBits
RefCountIsInline
RefCountBitsT
RefCountBitsInt
HeapObject初始化方法
Initialized
RefCountBits
RefCountBitsT
结论
二进制查看refCounts
SIL
copy_addr
strong_retain
increment方法
image.png
通过
查看`t`的引⽤计数
将
查看t1
使用
查看汇编代码
swift_weakInit
nativeInit
formWeakReference
allocateSideTable
InlineRefCountBits
二进制查看refCounts
HeapObjectSideTableEntry
SideTableRefCounts
SideTableRefCountBits
查看弱引用refCounts












网友评论