参考: 1、Object Runtime -- Weak 2、iOS 底层解析weak的实现原理 3、Objective-C runtime机制(7)
本文会从以下几个方面讲述weak的使用:
- 添加弱引用 (一个__weak修饰的变量,底层做了什么)
- weak有关的数据结构
- 删除弱引用 (一个对象的引用计数为0时执行dealloc,去除本对象的weak引用)
一、添加弱引用
首先看一个例子:
NSObject *obj = [[NSObject alloc] init];
__weak NSObject *weakObj = obj;
__weak NSObject *weakObj = obj,这句代码其实是调用了以下底层代码,具体可看runtime源码,NSObject.mm文件
1、id objc_initWeak(id *location, id newObj)
2、storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj)
3、storeWeak函数内部会调用 weak_register_no_lock()函数,该函数处理弱引用逻辑
1、首先调用 objc_initWeak 函数,函数内部会调用storeWeak函数;
2、在weak_register_no_lock()函数内部会获取弱引用对象所在的SideTable,SideTable中有weak_table_t结构体,weak_table中包含weak_entries数组,内部都是weak_entry_t结构体,每个对象所对应一个entry,entry管理该对象的弱引用关系数组
在看具体的代码之前先看下有关的数据结构:
二、有关的数据结构
1、StripedMap 内部装的SideTable
2、SideTable 封装了weak_table_t
3、weak_table_t 全局的weak表
4、weak_entry_t 对象所对应的弱引用
5、weak_referrer_t 弱引用
如看下图:
看下具体的数据结构:
1、StripedMap
// StripedMap<T> is a map of void* -> T, sized appropriately
// for cache-friendly lock striping.
// For example, this may be used as StripedMap<spinlock_t>
// or as StripedMap<SomeStruct> where SomeStruct stores a spin lock.
// StripedMap 其实是一个哈希表,
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize);
};
PaddedT array[StripeCount];
//hash值生成函数,根据对象的内存地址计算数组中具体的下标值
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
...
}
2、
struct SideTable {
spinlock_t slock; //自旋锁
RefcountMap refcnts; //对象-引用计数hash表 根据对象的指针获得引用计数
weak_table_t weak_table; // weak表,内存管理的核心数据结构 类的弱引用关系都放在 weak_entries中
SideTable() { // 初始化
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() { // 析构函数
_objc_fatal("Do not delete SideTable.");
}
...
}
3、weak_table_t
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
* 全局的weak表
*/
struct weak_table_t {
weak_entry_t *weak_entries; // entry数组
size_t num_entries; // 元素个数
uintptr_t mask; // 数组的最大下标值,用于辅助计算
uintptr_t max_hash_displacement; // 用于辅助计算
};
4、weak_entry_t
struct weak_entry_t {
DisguisedPtr<objc_object> referent; // 弱引用指向的对象指针
// 共用体,当弱引用个数大于4时,使用可变数组referrers,当小于等于4时,使用定长数组inline_referrers
union {
struct {
weak_referrer_t *referrers; // 可变数组
uintptr_t out_of_line_ness : 2; // 标记是否越界
uintptr_t num_refs : PTR_MINUS_2; // 弱引用数量
uintptr_t mask; //最大下标,用于辅助计算弱引用所在index时使用
uintptr_t max_hash_displacement; //用于辅助计算弱引用所在index时使用
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
// 定长数组
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
// =运算符重载
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other)); // 内存copy this指向other的内存地址
return *this;
}
// 构造器
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent) // 赋值弱引用对象保存
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
添加弱引用的具体实现: runtime源码
// 静态方法
template <HaveOld haveOld, HaveNew haveNew,
enum CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
Class previouslyInitializedClass = nil; // 标记未初始化的类
id oldObj; // 旧值
SideTable *oldTable; // 旧SideTable
SideTable *newTable; // 新SideTable
// Acquire locks for old and new values. 为旧值新值获取锁
// Order by lock address to prevent lock ordering problems. 按地址获取锁 防止锁出现问题
// Retry if the old value changes underneath us. 重试 如果值发生改变
retry:
if (haveOld) { // 如果有旧值,从SideTbales哈希表中获取旧SideTable,否则赋值nil
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) { // 如果有新值 从SideTbales哈希表中获取新SideTable 否则赋值nil
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 加锁
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// 如果有旧值且 弱引用地址和对象地址不相同 姐锁执行retry
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.
// 通过确保弱引用对象是否有 -+initialized isa指针,来防止弱引用机制和 +initialized 机制发生死锁
if (haveNew && newObj) {
Class cls = newObj->getIsa();// 获取被指向对象的class对象,isa指针为对象的第一个元素,所有isa地址就是class的指针地址
// 不是已经初始化的类 加锁进行类初始化
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. 如果类已经完成了initialize初始化
// If this class is still running +initialize on this thread 如果类还在线程中执行initialize方法
// (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; // 标记为已经初始化类并 retry
goto retry;
}
}
// Clean up old value, if any.
if (haveOld) { // 如果有旧值,把对象的弱引用指针置为nil并从weak_table表中清掉
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 ? CrashIfDeallocating : ReturnNilIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (!newObj->isTaggedPointerOrNil()) {
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);
// This must be called without the locks held, as it can invoke
// arbitrary code. In particular, even if _setWeaklyReferenced
// is not implemented, resolveInstanceMethod: may be, and may
// call back into the weak reference machinery.
callSetWeaklyReferenced((id)newObj);// 设置弱引用标记位
return (id)newObj;
}
三、删除弱引用
当一个对象释放的时候,系统调用dealloc方法,其中有一步就是清掉当前对象的weak引用,流程如下:
1、dealloc()
2、_objc_rootDealloc(self)
3、obj->rootDealloc()
4、object_dispose()
5、objc_destructInstance()
6、objc_clear_deallocating()
7、clearDeallocating_slow()
8、weak_clear_no_lock(&table.weak_table, (id)this)
四、总结
1、弱引用存放在哪里?
存放在该对象所对应weak_entry_t中的referrers数组中
SideTable -> weak_table_t -> weak_entry_t -> referrers
2、weak修饰属性的流程和__weak修饰变量的流程相同吗
是一样的,只是调用的时机不同,weak修饰属性,是类加载设置成员变量的时候调用objc_storeWeak()函数,这个函数内部也是调用的storeWeak()函数
3、弱引用对象是在什么时候被置为nil的?
系统执行dealloc的时候,内部会处理
涉及到的知识点
1、哈希表
2、TaggedPointer
3、dealloc
4、锁
5、内存管理