一、关于代理修饰
对于weak
:指明该对象并不负责保持delegate
这个对象,delegate
这个对象的销毁由外部控制。
对于strong
:该对象强引用delegate
,外界不能销毁delegate
对象,会导致循环引用(Retain Cycles)
对于assign
:也有weak
的功效。但是assign
是指针赋值,不对引用计数操作,使用之后如果没有值nil
,可能会产生野指针;而weak
一旦不进行使用后,永远不会使用了,就不会产生野指针。
二、weak的实现原理
1、weak 基本用法
weak
是弱引用,用 weak
来修饰、描述所引用对象的计数器并不会增加,而且 weak
会在引用对象被释放的时候自动置为 nil
,这也就避免了野指针访问坏内存而引起奔溃的情况,另外 weak
也可以解决循环引用。
拓展:为什么修饰代理使用 weak
而不是用 assign
?
assign
可用来修饰基本数据类型,也可修饰 OC 的对象,但如果用 assign 修饰对象类型指向的是一个强指针,当指向的这个指针释放之后,它仍指向这块内存,必须要手动给置为 nil,否则会产生野指针,如果还通过此指针操作那块内存,会导致 EXC_BAD_ACCESS
错误,调用了已经被释放的内存空间;而 weak 只能用来修饰 OC 对象,而且相比 assign 比较安全,如果指向的对象消失了,那么它会自动置为 nil,不会导致野指针。
2.weak 原理概括
weak
表其实是一个哈希表,key
是所指对象的指针,value
是 weak
指针的地址数组。(value 是数组的原因是:因为一个对象可能被多个弱引用指针指向)
Runtime
维护了一张 weak
表,用来存储某个对象的所有的 weak
指针。
3.weak
原理实现过程三步骤
-
3.1 初始化开始时,会调用
objc_initWeak
函数,初始化新的weak
指针1指向对象的地址。
-
3.2 然后
objc_initWeak
函数里面会调用objc_storeWeak()
函数,objc_storeWeak()
函数的作用是用来更新指针的指向,创建弱引用表。
3.3 在最后会调用
clearDeallocating
函数。而clearDeallocating
函数首先根据对象的地址获取weak
指针地址的数组,然后紧接着遍历这个数组,将其中的数组开始置为nil
,把这个entry
从weak
表中删除,最后一步清理对象的记录。
拓展:详细步骤
初始化开始时,会调用 objc_initWeak
函数,初始化新的 weak
指针指向对象的地址。当我们初始化 weak
变量时,runtime
会调用NSObject.mm
中的 objc_initWeak
,而 objc_initWeak
函数里面的实现如下:
id objc_initWeak(id *location, id newObj) {
// 查看对象实例是否有效,无效对象直接导致指针释放
if (!newObj) {
*location = nil;
return nil;
}
// 这里传递了三个 Bool 数值
// 使用 template 进行常量参数传递是为了优化性能
return storeWeakfalse/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
通过上面代码可以看出,objc_initWeak()
函数首先判断指针指向的类对象是否有效,无效,直接返回;否则通过 storeWeak()
被注册为一个指向 value
的_weak
对象
objc_initWeak
函数里面会调用 objc_storeWeak()
函数,objc_storeWeak()
函数的作用是用来更新指针的指向,创建弱引用表。
在最后会调用 clearDeallocating
函数。而 clearDeallocating
函数首先根据对象的地址获取 weak
指针地址的数组,然后紧接着遍历这个数组,将其中的数组开始置为 nil
,把这个 entry
从weak
表中删除,最后一步清理对象的记录。
问:当 weak
指向的对象被释放时,如何让 weak
指针置为nil
的呢?
答:
调用 objc_release
<1. 因为对象的引用计数为0,所以执行 dealloc
<2. 在dealloc
中,调用了 _objc_rootDealloc
函数
<3. 在 _objc_rootDealloc
中,调用了 object_dispose
函数
<4. 调用objc_destructInstance
<5. 最后调用objc_clear_deallocating
,详细过程如下:
a. 从 weak
表中获取废弃对象的地址为键值的记录
b. 将包含在记录中的所有附有 weak
修饰符变量的地址,赋值为nil
c. 将 weak
表中该记录删除
d. 从引用计数表中删除废弃对象的地址为键值的记录