weak指针, 也就是弱引用是OC中非常重要的概念, 相对于OC对象的alloc, retain,release, dealloc等内存管理的知识点, 弱引用weak指针的理解更加困难, 因此这里专门通过runtime的源码分析来归纳总结一下OC中弱引用相关的内容
先贴一张图:
1. weak指针(弱引用)的生命周期函数
从objc-runtime的源码中直接copy如下代码, 包括以下内容, 注意方法中两个参数分别是弱引用指针的地址, 弱引用指向的oc对象:
- 弱引用weak的初始化
objc_initWeak - 弱引用weak赋值
objc_storeWeak - 弱引用weak的释放
objc_destroyWeak - 以及使用弱引用指针使用的
objc_loadWeak
下面解释了两种常见的场景中初始化弱引用的方法, 两种场景分别是:
// 第一种场景, 初始化一个指向nil的弱引用指针
__weak id weakPtr;
// 第二种场景, 初始化一个指向 non-nil 的弱引用指针
NSObject *o = [NSObject new];
__weak id weakPtr = o;
他们都会触发以下方法, 其中
- 第一个参数是弱引用指针的地址!!! 注意一定是地址
- 第二个参数是弱引用指针要指向的OC对象!!!
/*
参数1: location Address of __weak ptr.
参数2: newObj Object ptr.
*/
id objc_initWeak(id *location, id newObj);
该方法的具体代码逻辑只用了两步, 源码中有本人的注释:
- 检查赋值或者初始化给弱引用指针的OC对象是否是nil, 如果是nil, 弱引用指针直接指向nil.
- 否则, 直接调用
storeWeak方法, 这是一个c++模板方法, 因此这里是初始化一个弱引用指针, 那么模板参数用的DontHaveOld,DoHaveNew,DoCrashIfDeallocating
/**
* Initialize a fresh weak pointer to some object location.
* It would be used for code like:
*
* (The nil case)
* __weak id weakPtr;
* (The non-nil case)
* NSObject *o = ...;
* __weak id weakPtr = o;
*
* This function IS NOT thread-safe with respect to concurrent
* modifications to the weak variable. (Concurrent weak clear is safe.)
*
* @param location Address of __weak ptr.
* @param newObj Object ptr.
*/
id objc_initWeak(id *location, id newObj) {
// 先判断弱引用指针指向的OC对象是否是空的, 如果是nil
// 那么弱引用指针指向的值是 nil!!!
// 此时本方法返回 nil
if (!newObj) {
*location = nil;
return nil;
}
// location 是 __weak ptr 也就是弱引用指针的地址!!!
// 强制将 newObj 转化成 objc_object * 对象
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
// 修改弱引用指针指向, 会指向一个新的oc对象
id objc_storeWeak(id *location, id newObj) {
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object *)newObj);
}
// 弱引用指针清理方法, 也即
// __weak NSObject *weakObj = oc 对象;
// weakObj = nil;
void objc_destroyWeak(id *location) {
// 释放以后才会调用 objc_destoryWeak 方法
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
上面的几个方法, 最终会触发一个c++的模板方法storeWeak<...>, 该模板方法本质是更新一个弱引用指针本身的值,其中模板参数有如下几个:
HaveOld, 表示弱引用指针当前是否指向了一个旧OC对象, 称为old object, 如果有, 那么后续需要先处理old object的弱引用表中的的本弱引用指针的信息HaveNew表示弱引用指针是否将要指向一个新的OC对象, 称为 new objectDoCrashIfDeallocating表示, 如果弱引用指针将要指向的new object 正在deallocating, 那么会crash!!! 这里简单解释就是下面这种写法会crash, 因此在调用对象的dealloc方法时, 该OC对象的isa.deallocating == true.
-(void)dealloc{
__weak typeof(self) weakself = self;
}
关键的更新弱引用变量的方法如下:
// Update a weak variable.
// If HaveOld is true, the variable has an existing value
// that needs to be cleaned up. This value might be nil.
// If HaveNew is true, there is a new value that needs to be
// assigned into the variable. This value might be nil.
// If CrashIfDeallocating is true, the process is halted if newObj is
// deallocating or newObj's class does not support weak references.
// If CrashIfDeallocating is false, nil is stored instead.
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
enum CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
// 需要先获取 old new oc对象的弱引用表
// oc对象 -> oc对象的sidetable -> weak_table -> weak_entry [oc对象, 弱引用指针地址]
Class previouslyInitializedClass = nil;
id oldObj;
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.
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;
}
// 如果 new oc 对象还没有初始化, 先初始化
...
// old new oc对象的sidetable 都上锁
// 先从 old oc 对象的 weak_table 中删除与本弱引用指针关联的weak_entry_t
// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// 然后, 用[new oc对象, 弱引用指针的地址]构建 weak_entry_t, 添加到new oc对象的sidetable的weak_table中
// 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
// 如果 new oc对象不是 taggedPointer 对象
// 对这个对象的 isa中的 weaklyReference 标志位设置一下
// Set is-weakly-referenced bit in refcount table.
if (!newObj->isTaggedPointerOrNil()) {
newObj->setWeaklyReferenced_nolock();
}
// 将弱引用指针指向 new oc 对象!!!
// 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);
// 强制设置一下 new obj 有一个弱引用指针指向的 weaklyReference 标志位
// 这个标志位标记以后, 在 new obj 对象释放的时, 通过该标志位判断是否有弱引用指针指向new obj, 如果有, 需要处理 weak_table
// 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);
// 返回的指针, 弱引用指针指向的oc对象!!!
return (id)newObj;
}
其实还有一种场景, block 中使用strongA = weakB的场景, 或者使用弱引用指针的地方, 会调用如下方法:
/**
* This loads the object referenced by a weak pointer and returns it, after
* retaining and autoreleasing the object to ensure that it stays alive
* long enough for the caller to use it. This function would be used
* anywhere a __weak variable is used in an expression.
*
* @param location The weak pointer address
*
* @return The object pointed to by \e location, or \c nil if \e location is \c nil.
*/
id objc_loadWeak(id *location) {
// 弱引用指针指向的oc对象是 nil了. 直接返回 nil
if (!*location) return nil;
// 否则弱引用指向的oc对象, 然后添加到 auto release pool 并返回回去!
return objc_autorelease(objc_loadWeakRetained(location));
}
从以上代码中能看到, 实际上oc中的弱引用指针/强引用指针与C++的指针最大的区别在于对oc对象的内存管理方面的处理!
除此以外, 弱引用/强引用指针与C++的普通指针并没有本质区别, 都是指向一个堆区的地址空间!!!
当我们使用oc的弱引用/强引用指针时, 编译器和runtime在底层悄悄维护着oc对象的引用计数信息和弱引用表!!!
上面内容中关键的方法如下:
- sideTable相关的API
weak_unregister_no_lockweak_register_no_lock
2. sideTable 的结构
首先, 在上述的源码中, 能看到获取一个oc对象的sidetable都是通过如下方法:
SideTable *table = &SideTables()[obj];
而其中关键的就是:
这里面涉及SideTables 这个全局的静态方法:
// runtime 中为了 SideTablesMap 这个静态对象能快速初始化,使用ExplicitInit类模板
static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;
// 可以简单理解成全局 static 的 StripedMap<SideTable> 对象
static StripedMap<SideTable>& SideTables() {
// get() 方法是 ExplicitInit模板中的方
return SideTablesMap.get();
}
// 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.
template<typename T>
class StripedMap {
enum { CacheLineSize = 64 };
struct PaddedT {
T value alignas(CacheLineSize);
};
enum { StripeCount = 8 };
PaddedT array[StripeCount];
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}
constexpr StripedMap() {}
};
从上述的源码中很清楚看清:
从源码中看出:
StripedMap<T>是一个类模板, 它本质是是一个散列表!!! 底层的数据容器使用的内存空间连续的array, 并且它含有8个元素, 使用的hash()散列函数依赖一个const void *指针, 底层会直接拿到指针指向的地址作为散列函数计算基础, 添加一些算数运算得到index!!!StripedMap<T>重写下表运算符[], 只要传入oc对象的地址, 就能通过散列函数, 计算具体的关联的T对象- runtime使用一个全局的静态的
StripedMap<SideTable>的对象, 来管理所有oc对象的SideTable, 只要oc对象的地址通过散列函数计算出数组的index, 就能获取oc对象对应的sideTable
runtime 中很多获取objc_object对象的sidetable, 使用如下方法:
SideTable& table = SideTables()[this];
全局SideTables散列表相关小结:
- 数据结构:
sideTables的底层数据结构是HashTable散列表 - 底层容器: 在静态数据区, 拥有8个元素的不可变array数组, 成员是
SideTable - 散列函数: 使用
objc_object*对象的地址结合一定算法作为散列函数, 计算index - KV信息:key是上面计算的index, value 是oc对象的
sideTable - 散列冲突: 表示不同的oc对象会共用
SideTable!!! - Hash扩展因子 : 无(固定长度数组)
对于SideTable的结构体:
struct SideTable {
spinlock_t slock; // 自旋锁, 用来保护 refcnts 和 weak_table 的细粒度锁
RefcountMap refcnts; // 额外的引用计数表 -- 散列Map
weak_table_t weak_table; // 弱引用表成员 -- 散列表
... // 一些c++ 构造函数, 析构, 锁方法等
}
简单点说前面runtime源码中涉及到的弱引用, 操作的都是sideTable中的weak_table表, 简单来说就是注册与移除, 这里都会涉及到弱引用表的结构!!!
// 从oc对象的弱引用表中注册弱引用指针的地址信息
weak_register_no_lock(&newTable->weak_table, (id)newObj, location, CrashIfDeallocating);
// 从oc对象的弱引用表中移除弱引用指针的地址信息
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
3. weak_table_t 弱引用表的结构
从前文看到弱引用weak指针的操作, 本质上runtime会在底层对weak指针指向的oc对象的弱引用表和isa进行一系列的操作, 在了解这些操作时, 必须对 weak_table的数据结构有一个清晰的认识.
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries; // 存储的实际的 key-value 键值对, 底层容器是可变array
size_t num_entries; // 当前存储的实体数量
uintptr_t mask; // 散列表的最大容量
uintptr_t max_hash_displacement;//当前最大散列碰撞次数
};
// 弱引用表中实际存储的 key-value 信息
// kv内容简写成: (object, weak pointer)
struct weak_entry_t {
// key --> oc对象指针
DisguisedPtr<objc_object> referent;
// value -> 用一个数组, 存储 weak pointer, 也就是弱引用指针!!!
// 使用 union 联合体节省内存
union {
struct {
weak_referrer_t *referrers; // HashTable 存储弱引用指针的可变数组!!!
uintptr_t out_of_line_ness : 2; // 判断使用的固定数组 还是 HashTable
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; // 存储弱引用指针的固定数组
};
};
... // 其他方法
};
// The address of a __weak variable. --> 表示 __weak 变量的地址!!! 弱引用指针的地址
// 这里用混淆方式, 为了减少对 memory analysis tools 的影响
typedef DisguisedPtr<objc_object *> weak_referrer_t;
weak_table总结如下:
- 数据结构:
weak_table_t的底层数据结构是HashTable散列表,它会被sideTable的spinlock进行保护 - 底层容器: 分配在堆上的可变的循环array数组, 每个成员是
weak_entry_t结构体 - 散列函数: 依赖
weak_entry_t的referent, 也就是oc对象计算出来的一个index!!! - KV信息: key是上面计算的index, value 是
oc对象的weak_entry_t结构体!!! - 散列冲突: 当发生散列冲突时, 类似于开放寻址法, 查找array的下一个空的位置存储
weak_entry_t - Hash扩展因子 : HashTable初始容量是64, 增加到0.75时, 扩容成原来的2倍; 当减少时,
1/16时, 减少到原来1/2
其中weak_table的散列函数:
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
weak_table的散列冲突时的处理:
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask); // 散列函数
size_t index = begin;
size_t hash_displacement = 0;
// 注意这里是 weak_table->weak_entries!!! 不要和后面弄混淆
// 从array的 index 位置开始, 向后找第一个空的位置!!! (循环数组)
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask; // 循环查找!!!
if (index == begin)
bad_weak_table(weak_entries); // 一个空的没有, 直接crash!
hash_displacement++; // 记录散列冲突的最大次数
}
另外一个比较复杂的是 weak_entry_t 的内部的弱引用指针存储相关内容, runtime使用了一个union来优化内存. 当指向oc对象的弱引用指针个数为4个以内时, 使用固定长度array存储weak pointer, 超过以后使用HashTable(这里我们在后面解释)!!!
weak_entry_t总结如下:
- 数据结构:
weak_entry_t的是一对Pair!!! - Pair的结构: (oc对象: weak pointers), 并且指针都是混淆过的!!!
weak pointers使用联合体实现,- 如果
weak pointers数量不大于4, 那么使用定长数组存储弱引用指针 - 如果数量>4, 那么会使用
HashTable存储
- 如果
当weak pointers的数量超过4个时, runtime会使用HashTable来存储!!! 它的信息如下:
- 数据结构是HashTable散列表
- 底层容器: 分配在堆上的可变的循环array数组, 每个成员都是
weak pointer的混淆结果 - 散列函数: 依赖
weak pointer的地址!!也就是new_referrer计算出来的一个index!!! - KV信息: key是上面计算的index, value是指向
oc对象的weak pointer指针! - 散列冲突: 当发生散列冲突时, 类似于开放寻址法, 查找array的下一个空的位置存储
weak pointer - Hash扩展因子 : HashTable初始容量是8, 增加到0.75时, 扩容成原来的2倍
注意: 上面所有的
weak pointer都是混淆结果的代称
其中存储weak pointers的散列函数, :
//new_referrer 是 weak pointer 的地址
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
weak_entry的散列冲突时的处理:
// new_referrer 是新的 __weak NSObject *xx; 这个弱引用指针的 xx 的地址
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
// 这里注意, 查询的是 referrers 数组!!! 不要和前面 weak_table 弄混
// 从array的 index 位置开始, 向后找第一个空的位置!!! (循环数组)
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
后面会仔细拆分弱引用表HashTable的增加, 删除和清理方法
weak_register_no_lock: 向弱引用表插入(object, weak pointer)的weak_entry_t结构体weak_unregister_no_lock: 从弱引用表移除(object, weak pointer)的weak_entry_t结构体weak_clear_no_lock: 在oc对象析构的时候调用, 设置所有的weak pointer为nil
4. weak_table_t 弱引用表的增加操作
创建一个新的__weak NSObject*指向对象, 通过前面的文章, 流程如下:
objc_initWeakobjc_storeWeakweak_register_no_lock- 此时拥有. new oc 对象, 以及 weak pointer 的地址!!
- 构造 (oc对象, weak pointer)的
weak_entry_t, 添加到weak_table_t中
/**
* Registers a new (object, weak pointer) pair. Creates a new weak
* object entry if it does not exist.
*
* @param weak_table The global weak table.
* @param referent The object pointed to by the weak reference.
* @param referrer The weak pointer address.
*/
id weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions) {
objc_object *referent = (objc_object *)referent_id; // oc 对象
objc_object **referrer = (objc_object **)referrer_id; // weak pointer
// 如果是 taggedPointer , 不受 weak pointer 影响
if (referent->isTaggedPointerOrNil())
return referent_id;
// 如果 oc 对象正在 dealloc, 此时会crash
...
// now remember it and where it is being stored
// 两种情况:
// 1. 如果 oc 对象之前已经有 weak pointer 指向, 那么添加到该 weak_entry 中
// 2. 如果没weak pointer, 那么新建一个(oc对象, weak pointer)的weak_entry, 插入到weak_table中
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
} else {
weak_entry_t new_entry(referent, referrer);
// 如果需要新插入 weak_entry_t, 需要判断 hashtable 是否需要增长
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
很常见的两种情况, 两条路的走法不一样:
NSObject *obj = [NSObject new];
__weak NSObject* weakObj1 = obj; // 情况1
__weak NSObject* weakObj2 = obj; // 情况2
- 如果当前
__weak NSObject*指向的oc对象没有弱引用指针指向, 可能需要对weak_table进行扩表, 然后将新的weak_entry插入weak_table中. - 如果当前
__weak NSObject*指向的oc对象之前有弱引用指针指向, 此时它的weak_table中的weak_entry_t肯定存在. 因此需要, 先找到weak_entry_t, 再讲weak pointer使用append_referrer加入进去
先分析第一种, 插入新的weak_entry_t 到weak_table中
这种情况也就是:
__weak NSObject* weakObj = [NSObject new];
具体的代码如下, 内部保留着注释:
// 表长度, 就是 mask
#define TABLE_SIZE(weak_table) (weak_table->mask ? weak_table->mask + 1 : 0)
// Grow the given zone's table of weak references if it is full.
static void weak_grow_maybe(weak_table_t *weak_table) {
// 获取旧值
size_t old_size = TABLE_SIZE(weak_table);
// HashTable 的扩张因子阈值是 0.75
if (weak_table->num_entries >= old_size * 3 / 4) {
// 扩张的大小是 *2, 初始时是 64!!!!
weak_resize(weak_table, old_size ? old_size * 2 : 64);
}
}
// 扩表操作
static void weak_resize(weak_table_t *weak_table, size_t new_size) {
size_t old_size = TABLE_SIZE(weak_table);
// 旧数据
weak_entry_t *old_entries = weak_table->weak_entries;
// 重新分配 new_size 的堆空间
weak_entry_t *new_entries = (weak_entry_t *)
calloc(new_size, sizeof(weak_entry_t));
weak_table->mask = new_size - 1;
weak_table->weak_entries = new_entries;
weak_table->max_hash_displacement = 0;
weak_table->num_entries = 0; // restored by weak_entry_insert below
// 将旧的 weak_entry 结构体, 一个个插入新的空间
if (old_entries) {
weak_entry_t *entry;
weak_entry_t *end = old_entries + old_size;
for (entry = old_entries; entry < end; entry++) {
// 注意 resize的时, 如果 weak_entry中的 oc对象不存在(可能被释放了), 那么不插入到新的
if (entry->referent) {
weak_entry_insert(weak_table, entry);
}
}
free(old_entries);
}
}
/**
* Add new_entry to the object's table of weak references.
* Does not check whether the referent is already in the table.
*/
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry) {
weak_entry_t *weak_entries = weak_table->weak_entries;
ASSERT(weak_entries != nil);
// 使用 散列函数!!! 计算循环可变数组array 的 index!!!
// 散列函数使用的是 oc对象的地址计算的!!!
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
if (index == begin)
bad_weak_table(weak_entries);
hash_displacement++; // 散列冲突了. 继续找空位
}
// 正式插入!!!
weak_entries[index] = *new_entry; // 使用拷贝构造函数!!!
// 一共插入的 weak_entry_t 的数量, 使用 weak_table->num_entries 存储
weak_table->num_entries++;
// 更新散列函数冲突最大出力次数
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}
从以上代码中能看到, 如果是完全初始化的weak_table第一次插入weak_entry_t, 那么会构造这个HashTable, 在底层构造初始长度为64的可变长的循环array作为散列表的容器. 其他weak_table散列表的内容上文以及总结了.
再分析第二种, 插入的weak_entry_t 中的oc对象之前已经有弱引用指指向了
情况二的内容:
NSObject *obj = [NSObject new];
__weak NSObject* weakObj1 = obj; // 情况1
__weak NSObject* weakObj2 = obj; // 情况2
关键代码如下:
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
weak_entry_for_referent 是为了找到oc对象的weak_entry_t源码如下, 代码比较简单, 直接参考注释:
/**
* Return the weak reference table entry for the given referent.
* If there is no entry for referent, return NULL.
* Performs a lookup.
*
* @param weak_table
* @param referent The object. Must not be nil.
*
* @return The table of weak referrers to this object.
*/
static
weak_entry_t* weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) {
ASSERT(referent);
// 获取HashTable
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
// 使用Hash函数, 获取 index
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
// 将 index 结合判断是否发生 hash碰撞, 找到查询的 index
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin)
bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
// 返回 oc对象关联的 weak_entry!!!
return &weak_table->weak_entries[index];
}
比较复杂的是给weak_entry_t 新增一个新的weak pointer, 核心函数是append_referrer:
// The address of a __weak variable.
// These pointers are stored disguised so memory analysis tools
// don't see lots of interior pointers from the weak table into objects.
typedef DisguisedPtr<objc_object *> weak_referrer_t; //
/**
* Add the given referrer to set of weak pointers in this entry.
* Does not perform duplicate checking (b/c weak pointers are never
* added to a set twice).
*
* @param entry The entry holding the set of weak pointers.
* @param new_referrer The new weak pointer to be added.
函数的两个关键入参, 分别是 oc对象的weak_entry!! 另一个参数是新的指向该oc对象的弱引用指针的地址
*/
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) {
// out_of_line() 函数判断 weak_entry中管理 弱引用指针的方式使用的 HashTable 还是定长数组!!!
if (!entry->out_of_line()) {
// 表示当前使用的定长数组, 如果定长数组还有空间, 那么直接将弱引用指针的地址存储到定长数组的空位成, 然后返回即可
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// 此时定长数组中没有剩余空间存储 新的 弱引用指针的地址!!!
// 需要更换底层容器, 将定长数组存储, 改造成 HashTable存储
// Couldn't insert inline. Allocate out of line.
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
// This constructed table is invalid, but grow_refs_and_insert
// will fix it and rehash it.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[i];
}
entry->referrers = new_referrers;
entry->num_refs = WEAK_INLINE_COUNT;
entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
entry->mask = WEAK_INLINE_COUNT-1;
entry->max_hash_displacement = 0;
}
// 指定一下此时一定是 HashTable 方式存储 weak pointers
ASSERT(entry->out_of_line());
// 如果是首次变化成 HashTable存储 或者 已经超过容积因子0.75
// 需要对HashTable扩容, 增大2 * oldSize. 然后重新 调用 append_referrer 插入 new_referrer
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
// Hash函数!!! 使用的是 weak pointer的地址关联的函数左右散列函数
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
// Hash 碰撞时, 向下找第一个空的位置(注意是一个循环可变长的array)
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
// 记录hash碰撞的最大值
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
// 存储在找到的index 后面的第一个空位置, 然后存储
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
/**
* Grow the entry's hash table of referrers. Rehashes each
* of the referrers.
*
* @param entry Weak pointer hash set for a particular object.
*/
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry,
objc_object **new_referrer)
{
// 如果是增长, 必须 weak_entry_t 使用循环可变长array为容器的 HashTable 存储weak pointer的地址
ASSERT(entry->out_of_line());
size_t old_size = TABLE_SIZE(entry);
size_t new_size = old_size ? old_size * 2 : 8;
size_t num_refs = entry->num_refs;
weak_referrer_t *old_refs = entry->referrers;
entry->mask = new_size - 1;
entry->referrers = (weak_referrer_t *)
calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
entry->num_refs = 0;
entry->max_hash_displacement = 0;
// 所有的通过 Hash函数重新计算, 插入新的 HashTable中
for (size_t i = 0; i < old_size && num_refs > 0; i++) {
if (old_refs[i] != nil) {
append_referrer(entry, old_refs[i]);
num_refs--;
}
}
// Insert 插入新的 weak ponter 的地址
append_referrer(entry, new_referrer);
if (old_refs) free(old_refs);
}
对于这里存储 weak pointers 地址的HashTable的关键信息, 这里再贴一遍:
weak_entry_t总结如下:
- 数据结构:
weak_entry_t的是一对Pair!!! - Pair的结构: (oc对象: weak pointers), 并且指针都是混淆过的!!!
weak pointers使用联合体实现,- 如果
weak pointers数量不大于4, 那么使用定长数组存储弱引用指针 - 如果数量>4, 那么会使用
HashTable存储
- 如果
当weak pointers的数量超过4个时, runtime会使用HashTable来存储!!! 它的信息如下:
- 数据结构是HashTable散列表
- 底层容器: 分配在堆上的可变的循环array数组, 每个成员都是
weak pointer的混淆结果 - 散列函数: 依赖
weak pointer的地址!!也就是new_referrer计算出来的一个index!!! - KV信息: key是上面计算的index, value是指向
oc对象的weak pointer指针! - 散列冲突: 当发生散列冲突时, 类似于开放寻址法, 查找array的下一个空的位置存储
weak pointer - Hash扩展因子 : HashTable初始容量是8, 增加到0.75时, 扩容成原来的2倍
注意: 上面所有的
weak pointer都是混淆结果的代称
5. weak_table_t 弱引用表的删除操作
上面已知了弱引用表的增加操作, 之类代码类似, 删除操作就不详细展开了.
/**
* Unregister an already-registered weak reference.
* // 弱引用之前已经是注册在全局的 弱引用表, 这里需要从表中移除
* // 从表中移除的操作
* This is used when referrer's storage is about to go away, but referent
* isn't dead yet. (Otherwise, zeroing referrer later would be a
* bad memory access.)
* Does nothing if referent/referrer is not a currently active weak reference.
* Does not zero referrer.
*
* FIXME currently requires old referent value to be passed in (lame)
* FIXME unregistration should be automatic if referrer is collected
*
* @param weak_table The global weak table.
* @param referent The object.
* @param referrer The weak reference.
*/
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id) // 新的weak指针的地址
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
// 弱引用实体的指针
weak_entry_t *entry;
if (!referent) return;
// 旧实体对象, 需要传入 referent, 也就是 引用对象
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 从实体里面移除弱引用指针
remove_referrer(entry, referrer);
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
} else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
// 判断 弱引用实体是否已空了
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
/**
* Remove old_referrer from set of referrers, if it's present.
* Does not remove duplicates, because duplicates should not exist.
*
* @todo this is slow if old_referrer is not present. Is this ever the case?
*
* @param entry The entry holding the referrers.
* @param old_referrer The referrer to remove.
*/
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (! entry->out_of_line()) {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
hash_displacement++;
if (hash_displacement > entry->max_hash_displacement) {
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
}
entry->referrers[index] = nil;
entry->num_refs--;
}
6. weak_table_t 弱引用表的清理操作
当弱引用指针指向的oc对象的引用计数为0时, 会被release方法调用dealloc方法, 在dealloc中会调用weak_clear_no_lock方法, 通俗点说, 就是
- 需要将弱引用表中的弱引用指针全部指向nil!!!
- 将该oc对象的 weak_entry_t 结构体从弱引用表中移除!!!
- 可能会对 weak_table的HashTable缩表!!!
/**
1. Called by dealloc;
2. nils out all weak pointers that point to the provided object so that they can no longer be used.
*
* @param weak_table
* @param referent The object being deallocated.
*/
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
} else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
// 获取到弱引用指针的地址, 以后,通过地址获取弱引用指针的值并设置成nil!!!
*referrer = nil;
} else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
// 从弱引用表中将 weak_entry_t 移除
// 根据情况判断 弱引用表的HashTable是否要缩表
weak_entry_remove(weak_table, entry);
}
总结
- 全局使用固定长度Array, 通过HashTable 维护 SideTables
- 使用oc对象的地址关联的Hash函数, 获取对应的SideTable
- 使用SideTable中的spinlock保护oc对象的弱引用表
weak_table - 弱引用表
weak_table使用HashTable数据结构维护weak_entry_t的(oc对象, weak pointers) pair数据 - 弱引用表
weak_table使用的Hash函数是oc对象的地址关联的Hash函数, 获取对应的weak_entry_t weak_entry_t根据内部管理的weak pointers的数量, 使用两种方式管理weak pointer- weak pointers 数量少于或等于4时, 使用固定长度数组, 遍历方式管理
- weak pointers 数量大于4, 使用HashTable管理, 底部是一个循环可变长array
- 它使用weak pointer的地址关联函数计算index
- 然后使用开放寻址方式解决Hash冲突
一句话小结!!!
- weak指针管理中使用了3个HashTable!!!
- 其中两个使用oc对象作为index!!!还有一个使用弱引用指针的地址作为index计算方式
- 除了全局SideTables(), 其他两个的HashTable都使用开放寻址法作为Hash碰撞解决方法!!!