Swift进阶-类与结构体
Swift-函数派发
Swift进阶-属性
Swift进阶-指针
Swift进阶-内存管理
Swift进阶-TargetClassMetadata和TargetStructMetadata数据结构源码分析
Swift进阶-Mirror解析
Swift进阶-闭包
Swift进阶-协议
Swift进阶-泛型
Swift进阶-String源码解析
Swift进阶-Array源码解析
在类与结构体章节讲到:通过汇编调试和swift源码知道我们的 纯swift类的对象 Teacher()
的内存分配: __allocating_init -> _swift_allocObject_ -> swift_slowAlloc -> malloc
其中_swift_allocObject_
里面创建一个 HeapObject
对象并将其返回了,所以这个HeapObject
就是我们实际创建的对象,来看看其初始化函数:
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
了解上面内容后,来看看创建一个对象,输出它的地址,格式化输出它的内容:
class Teacher {
var name: String
init(name: String) {
self.name = name
}
}
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
let teacher = Teacher(name: "陈老师")
// 通过Unmanaged指定内存管理,类似于OC与CF的交互方式(所有权的转换 __bridge)
// passUnretained 不增加引用计数,即不需要获取所有权
// passRetained 增加引用技术,即需要获取所有权
// toOpaque 不透明的指针
let ptr = Unmanaged.passUnretained(teacher as AnyObject).toOpaque()
print(ptr)
print("end") // 断点在这里
}
}
通过控制台输出后得到上图,而x/8g输出的前16个字节就是matadata
和refcounts
,这里的refcounts就是本章节研究的内容。
ps: 注意此时要用Unmanaged.passUnretained
去输出teacher
的指针,而不能在控制台上 po teacher
!因为 po teacher
会对其进行引用,就不是上面这个结果了,来看看:
本章节围绕一个问题探究引用计数:那为什么一次引用后会变成这个数值0x0000000200000002
?
1.引用计数的实质
从源码中HeapObject.h
的找到refcounts
的定义
RefCounts
其实就是对我们当前引用计数的包装类,而引用计数的具体类型取决于传递给RefCounts
的参数 -> InlineRefCountBits
。
找到InlineRefCountBits
的定义:
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
它同样是一个模板类RefCountBitsT
RefCountIsInline
传递的是true/false,再去看看模板类RefCountBitsT
:
RefCountBitsT
就是我们真实操作引用计数的类,它只有一个属性 bits
,它的类型定义是 RefCountBitsInt
引用计数的实质: bits
其实就是一个uint64位的位域信息,它存储了就是运行周期相关的引用计数(Swift和OC都是一样的64位的位域信息)。
1.1 提问:当创建一个引用对象的时候,它的引用计数是多少?
来看看源码中的创建对象函数_swift_allocObject_
引用计数的初始化赋值,传递了一个Initialized
,点进去看看Initialized
定义,在RefCount.h
找到它是一个枚举:
可以看到源码的注释:新对象的Refcount为1。
RefCountBits
就是模板类RefCountBitsT
(它有一个bits属性)
找到RefCountBitsT
初始化函数,很快能定位到传递的参数strongExtraCount: 0
和unownedCount: 1
是怎么操作bits
的:
0左移33位还是0,1左移1位是2,所以refCounts是0x2。
当声明一个引用对象,该对象没有被引用的时候,refCounts是0x0000000000000002:
let teacher = Teacher(name: "陈老师")
let ptr = Unmanaged.passUnretained(teacher as AnyObject).toOpaque()
print(ptr)
print("end")
当teacher被引用的时候,其refCounts是0x0000000200000002:
let teacher = Teacher(name: "陈老师")
let ptr = Unmanaged.passUnretained(teacher as AnyObject).toOpaque()
print(ptr)
print("end")
let t = teacher
print("end")
1左移33位,1左移1位是2,所以refCounts是0x0000000200000002。
如果再被引用一次,其refCounts是0x0000000400000002
2左移33位,1左移1位是2,所以refCounts是0x0000000400000002。
结论:强引用计数
和无主引用计数
是通过位移的方式,存储在这64位的信息当中。
1.2 提问:强引用是如何添加的呢?
从swift源码中找到HeapObject.cpp
找到_swift_retain_
函数实现:
引用计数+1:
引用计数-1:
2.循环引用问题
经典案例:
class WJTeacher {
var age: Int = 18
var name: String = "林老师"
var subject: WJSubject?
}
class WJSubject {
var subjectName: String
var subjectTeacher: WJTeacher
init(_ subjectName: String, _ subjectTeacher: WJTeacher) {
self.subjectName = subjectName
self.subjectTeacher = subjectTeacher
}
}
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
let teacher = WJTeacher()
let subject = WJSubject.init("Swift进阶", teacher)
teacher.subject = subject
}
}
上面这段代码两个对象相互引用导致无法释放。
Swift 提供了两种办法⽤来解决你在使 ⽤类的属性时所遇到的循环强引⽤问题:弱引⽤( weak reference )
和⽆主引⽤( unowned reference )
。
weak弱引用
解决上面的问题:
class WJTeacher {
var age: Int = 18
var name: String = "Steven"
weak var subject: WJSubject? // 弱引用subject
}
class WJSubject {
var subjectName: String
var subjectTeacher: WJTeacher
init(_ subjectName: String, _ subjectTeacher: WJTeacher) {
self.subjectName = subjectName
self.subjectTeacher = subjectTeacher
}
}
或者使用unowned无主引用
解决上面的问题
class WJTeacher {
var age: Int = 18
var name: String = "Steven"
var subject: WJSubject?
}
class WJSubject {
var subjectName: String
unowned var subjectTeacher: WJTeacher // 无主引用subjectTeacher
init(_ subjectName: String, _ subjectTeacher: WJTeacher) {
self.subjectName = subjectName
self.subjectTeacher = subjectTeacher
}
}
弱引用和无主引用有什么区别呢?
2.1 weak
弱引用
Alaways Show Disassembly打开编调试,看看weak
修饰对象在创建时调用流程
weak var t = WJTeacher()
print(t) // 断点打在这里
在类与结构体章节看过__allocating_init()
的调用过程,没有看到相关弱引用的内容;接下来又调用了swift_weakInit
函数;找到swift源码:
来看看allocateSideTable
做了啥事儿
来找到side: HeapObjectSideTableEntry
到底是啥玩意儿,全局搜索了一下找到了源码中有这么一段注释,直接给出了结论:
在swift里面存在着两种引用计数,一种是强引用包含的是strong RC + unowned RC + flags
;一种是弱引用包含的是strong RC + unowned RC + weak RC + flags
来一下HeapObjectSideTableEntry
的源码:
可以发现弱引用与强引用共用一个模板类RefCounts
其实就是它:RefCountBitsT
。
来看看引用计数操作模板类RefCountBitsT
通过弱引用的方式创建的源码:
weak
方式创建RefCountBitsT
,首先是将散列表右移3位,然后将62位和63位标记为1。
通过逆反操作验证上面源码分析的的准确性:
weak引用后refCounts那8位返回的是一个内存地址(而不是引用计数64位域)
1.我们要还原,先将62位63位的标志位设置为0得到的结果:
2.还要将这个结果向左移动3位:
将这个结果在控制台上格式化输出:
weak引用一个对象本质上就是创建一个散列表
2.2 unowned
与weak
上面介绍了强引用计数和无主引用计数是通过位移的方式,存储在这64位的信息当中的。
-
unowned
不允许被引用的对象有nil的可能,无需要新建/维护散列表,直接可以通过64位的位域操作即可进行引用计数。 -
weak
它所引用的对象允许为nil,它需要新建/维护散列表
共同点:
- 引用对象的自动引用计数都不会加1,不会造成对引用对象的强引用。
解决block引用计数问题:
class WJTeacher {
var age: Int = 18
var name: String = "Steven"
var closure: (()->())?
deinit {
print("WJTeacher 消失")
}
}
func test() {
let t = WJTeacher()
var closure = {
t.age = 19
}
t.closure = closure
closure()
}
此时循环引用:t->closure->t
修改后:
class WJTeacher {
var age: Int = 18
var name: String = "Steven"
var closure: (()->())?
deinit {
print("WJTeacher 消失")
}
}
func test() {
let t = WJTeacher()
var closure = { [weak t] in
t?.age = 19
}
t.closure = closure
closure()
}
// 或者
/**
func test() {
let t = WJTeacher()
var closure = { [unowned t] in
t.age = 19
}
t.closure = closure
closure()
}
*/