一、weak基本介绍
weak
是弱引用,用weak
来修饰、描述所引用对象的计数器并不会加1,而且weak
会在引用对象被释放的时候自动置为nil
,这也就避免了野指针访问坏内存而引起奔溃的情况,另外weak
也可以解决循环引用。
二、weak原理概括
1、基本原理:
weak
表其实是一个哈希表,key
是所指对象的指针,value
是weak
指针的地址数组。(value
是数组的原因是:因为一个对象可能被多个弱引用指针指向)。同时Runtime
维护了一张weak
表,用来存储某个对象的所有的weak
指针。
2、weak原理实现过程三步骤
(1)第一步初始化,初始化会先调用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
对象
(2)第二步objc_initWeak
函数里面会调用objc_storeWeak()
函数,objc_storeWeak()
函数的作用是用来更新指针的指向,创建弱引用表。
(3)第三步调用clearDeallocating
函数,而clearDeallocating
函数首先根据对象的地址获取weak
指针地址的数组,然后紧接着遍历这个数组,将其中的数组开始置为nil
,把这个entry从weak
表中删除,最后一步清理对象的记录。
三、当weak指向的对象被释放时,如何让weak指针置为nil的呢?
1、调用objc_release
2、因为对象的引用计数为0,所以执行dealloc
3、在dealloc
中,调用了_objc_rootDealloc
函数
4、在_objc_rootDealloc
中,调用了object_dispose
函数
5、调用objc_destructInstance
6、最后调用objc_clear_deallocating
,详细过程如下:
(1)从weak
表中获取废弃对象的地址为键值的记录
(2)将包含在记录中的所有附有weak
修饰符变量的地址,赋值为nil
(3)将weak
表中该记录删除
(4)从引用计数表中删除废弃对象的地址为键值的记录
四、问题提问
1、为什么修饰代理使用weak
而不是用assign
或strong
?
首先用strong
修饰会引起循环引用,那句self.delegate = self;
,这个就是普通的循环引用问题。
至于assign
,首先大家都知道assign
修饰的是基础数据分布在栈区,系统自动管理内存不进行引用计数,而代理delegate
指向的对象是存储在堆区,离开作用域即便delegate
指向的对象(非oc对象)销毁了,delegate
中依然会保存之前对象的地址,所以使用之后如果没有置为nil
,可能会产生野指针。解决办法是weak
修饰之后自动置为nil
,就不会产生野指针。
举例说明:
如果用weak修饰,那么当viewController释放后,A对象的delegate会自动置为nil,如果用assign修饰,当viewController释放后,A对象的delegate会变成野指针,要防止这种情况,就要重写viewController的dealloc方法,手动将A对象的delegate置为nil.
- (void)dealloc{
self.A.delegate = nil;
};
综上所述:ARC与MRC下有什么不同?就是MRC下只能用assign修饰,而在ARC下既能用weak也能用assign,只是用assign要做多一定工作
2、assign
可用来修饰基本数据类型,也可修饰OC的对象,为什么不用assin
修饰对象
如果用assign
修饰对象类型指向的是一个强指针,当指向的这个指针释放之后,它仍指向这块内存,必须要手动给置为nil
,否则会产生野指针,如果还通过此指针操作那块内存,会导致EXC_BAD_ACCESS
错误,调用了已经被释放的内存空间;而weak
只能用来修饰OC对象,而且相比assign
比较安全,如果指向的对象消失了,那么它会自动置为nil,不会导致野指针。