iOS-底层原理26:weak原理
1、weak原理概括
weak是弱引用,用weak描述修饰或者所引用对象的计数器不会加一,并且会在引用的对象被释放的时候自动被设置为nil,大大避免了野指针访问坏内存引起的崩溃情况。
weak表明该属性定义了一种“非拥有关系”,为这种属性设置新值时,设置方法既不保留新值,也不释放旧值,此特质同assign
类似。
问题:
runtime
如何实现weak
变量的自动置nil
?
解答
:
runtime
将weak
对象放入一个hash
表中,key
是所指对象的指针,value
是weak
指针的地址数组(可能存在多个对象引用);
当对象的引用计数为0时会dealloc
,假如weak
指向的对象内存地址为a
,那么就会以a为键
在这个weak
表中搜索,找到所有以a为键的weak对象
,从而设置为nil。
2、weak原理实现步骤
weak
的实现原理可概括为三步:
1、
初始化时:runtime
会调用objc_initWeak
函数,初始化一个新的weak
指针指向对象的地址。
2、
添加引用时:objc_initWeak
函数会调用objc_storeWeak()
函数, objc_storeWeak()
的作用是更新指针指向,创建对应的弱引用表。
3、
释放时:调用clearDeallocating
函数,clearDeallocating
函数首先会根据对象地址获取所有weak
指针地址的数组,然后遍历这个数组把其中的数据设为nil
,最后把这个 entry
从 weak
表中删除,最后清理对象的记录。
3、源码分析
step1:
可以通过打断点查看汇编的方式查看weak
源码
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
step2:
可以在objc
源码中找到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
对象
step3:
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
函数把新的对象进行注册操作,完成与对应的弱引用表进行绑定操作
step4:
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表中删除,最后清理对象的记录。