一、基本用法
weak
是弱引用,用 weak
来修饰、描述所引用对象的计数器并不会增加,而且 weak
会在引用对象被释放的时候自动置为 nil
,这也就避免了野指针访问坏内存而引起奔溃的情况,另外 weak
也可以解决循环引用。
runtime 如何实现 weak
变量的自动置 nil
?
解答:
runtime 将 weak
对象放入一个 hash
表中,key
是所指对象的指针,value
是 weak
指针的地址数组(可能存在多个对象引用);
当对象的引用计数为 0 时会 dealloc
,假如 weak
指向的对象内存地址为 a,那么就会以 a 为键在这个 weak
表中搜索,找到所有以 a 为键的 weak
对象,从而设置为 nil
。
为什么修饰代理使用 weak
而不是用 assign
?
assign
可用来修饰基本数据类型,也可修饰 OC 的对象,但如果用 assign
修饰对象类型指向的是一个强指针,当指向的这个指针释放之后,它仍指向这块内存,必须要手动给置为 nil
,否则会产生野指针,如果还通过此指针操作那块内存,会导致 EXC_BAD_ACCESS
错误,调用了已经被释放的内存空间;而 weak
只能用来修饰 OC 对象,而且相比 assign
比较安全,如果指向的对象消失了,那么它会自动置为 nil
,不会导致野指针。
二、实现原理
Runtime
维护了一个 weak
表,用于存储指向某个对象的所有 weak
指针。weak
表其实是一个 hash(哈希)表,Key 是所指对象的地址,Value 是 weak
指针的地址 (这个地址的值是所指对象的地址)数组。
weak
的实现原理可以概括一下三步:
(1)初始化时:Runtime 会调用 objc_initWeak
函数,初始化一个新的 weak
指针指向对象的地址。
(2)添加引用时:objc_initWeak
函数会调用 objc_storeWeak()
函数,objc_storeWeak()
的作用是更新指针指向,创建对应的弱引用表。
(3)释放时:调用 clearDeallocating
函数。clearDeallocating
函数首先根据对象地址获取所有 weak
指针地址的数组,然后遍历这个数组把其中的数据设为 nil
,最后把这个 entry
从 weak
表中删除,最后清理对象的记录。
三、底层原理探索
以下案例就是使用一个临时变量 obc1 来存储创建出来的变量,代码如下:
NSObject * objc1 = [[NSObject alloc] init];
id __weak objc2 = objc1;
1.objc_initWeak
当我们初始化一个 weak
变量时,runtime 会调用 NSObject.mm 中的 objc_initWeak
函数。
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
这里先判断对象是否有效,无效直接释放返回;否则通过 storeWeak
函数注册一个指向为 value
的 __weak
对象。
2.storeWeak
static id
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
//声明新旧两个SideTable
SideTable *oldTable;
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
//根据新值和旧值分别获取全局的SideTables表,分别赋值给oldTable,newTable
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(cls, (id)newObj);
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
goto retry;
}
}
//清空旧值
// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
- 旧对象解除注册操作
weak_unregister_no_lock
该方法主要作用是将旧对象在 weak_table
中解除 weak
指针的对应绑定。根据函数名,称之为解除注册操作。从源码中,可以知道其功能就是从 weak_table
中接触 weak
指针的绑定。而其中的遍历查询,就是针对于 weak_entry
中的多张弱引用散列表。
- 新对象添加注册操作
weak_register_no_lock
这一步与上一步相反,通过 weak_register_no_lock
函数把新的对象进行注册操作,完成与对应的弱引用表进行绑定操作
3. weak
释放为 nil
过程
weak
被释放为 nil
,需要对对象整个释放过程了解,如下是对象释放的整体流程:
(1)调用 objc_release
(2)因为对象的引用计数为0,所以执行 dealloc
(3)在 dealloc
中,调用了 _objc_rootDealloc
函数
(4)在 _objc_rootDealloc
中,调用了 object_dispose
函数
(5)调用 objc_destructInstance
(6)最后调用 objc_clear_deallocating
。
对象准备释放时,调用 clearDeallocating
函数。clearDeallocating
函数首先根据对象地址(key)获取所有weak指针地址的数组(value),然后遍历这个数组把其中的数据设为 nil
,最后把这个 entry
从 weak
表中删除,最后清理对象的记录。
网友评论