SideTables
SideTables 是全局表,管理着对象的引用计数和weak引用指针,每个对象在此表中都有对应的一个 SideTable,让我们来看看 SideTables 源码定义
static StripedMap& SideTables() {
return *reinterpret_cast*>(SideTableBuf);
}
虽然看不懂,但从源码的定义可以看出 SideTables 是通过 StripedMap 来实现的,我们来看一下它的实现
template
class StripedMap {
enum { CacheLineSize = 64 };
#if TARGET_OS_EMBEDDED // 嵌入式
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize);
};
PaddedT array[StripeCount];
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast(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>(this)[p];
}
};
从上可以看出,StripedMap 的容量大小为:StripeCount = 64,通过 indexForPointer 函数分配在 StripedMap的下标。public 中的应该是读存方法吧(猜测,哈哈。。),可以看到其和 Map 集合一样。
SideTable
SideTable 的定义如下
typedef objc::DenseMap,size_t,true> RefcountMap;
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
...
};
- slock:是用于在对 SideTable 操作时,对 SideTable 加锁防止其他访问。
- refcnts:对象的引用计数器,存储着对象被引用的记录。
- weak_table_t: 存放弱变量引用
DisguisedPtr
class DisguisedPtr {
uintptr_t value;
// 对指针进行伪装
static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}
// 恢复至原指针
static T* undisguise(uintptr_t val) {
return (T*)-val;
}
};
DisguisedPtr 对指针进行伪装的类,将指针强转为 uintptr_t (unsigned long)类型的负值,这样类似 leaks 这样的查内存泄漏的工具便无法跟踪到对象。
weak_table_t
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
- weak_entries:存放对象与弱引用对象指针映射的弱引用条目数组
- num_entries:弱引用条目总数
- mask:可存储弱引用条目的容量
- max_hash_displacement:最大哈希偏移值
weak_entry_t
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
struct weak_entry_t {
DisguisedPtr referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};
weak_entry_t 是一个弱引用条目,其映射了引用对象和其被弱引用的指针,referent 便是引用对象,union(联合体) 里存放着弱引用该对象的指针,union 里面的多个成员变量共享同一内存空间。union 中有两个结构体都是存储弱引用对象指针的集合。第一个结构体中 referrers 是一个可进行扩容的集合,而第二个结构体中 inline_referrers 是一个容量为 4 的数组,weak_entry_t 默认使用 inline_referrers 来保存弱引用指针,当此数组容量满后,会使用 referrers 接管保存工作。out_of_line_ness 便是描述存储的弱引用指针是否超出 inline_referrers 的容量。
__weak 原理
NSString *aa = @"aa";
__weak NSString *test = aa;
上面代码在编译时,模拟的代码如下:
NSString *aa;
aa = @"aa"
NSString *test;
objc_initWeak(&obj, aa);
objc_destoryWeak(&obj);
__weak 变量创建
__weak 变量的创建入口是 objc_initWeak 这个函数,其实现是:
id objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak
(location, (objc_object*)newObj);
}
如果 __weak 变量被赋予的对象是 nil 那么,将 __weak 变量置 nil,进入 objc_destoryWeak 销毁函数。storeWeak 函数是一个更新弱变量的函数,此函数有点长,我们分段讲述:
// 如果 HaveOld 为 true,则表明变量需要清理,变量可能为nil
// 如果 HaveNew 为 true,则表明有一个新值将赋予变量,这个新值可能为 nil
// 如果 CrashIfDeallocating 为 true,则表明新值 newObj 是释放了的对象(并不是说 newObj 为 nil)或者是一个不支持弱引用的对象。
// 如果 CrashIfDeallocating 为 false,则将新值 newObj 置nil 并 *location 弱变量赋值为 nil。
template
static id
storeWeak(id *location, objc_object *newObj)
{
assert(HaveOld || HaveNew);
if (!HaveNew) assert(newObj == nil);
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;
}
声明一个 previouslyInitializedClass 保存先前初始化的类,声明一个旧值对象 oldObj,一新一旧两个 SideTable(散列表)。从 objc_initWeak 传入的 HaveOld 为 false,HaveNew 为 true,因此将 oldTable 赋值为 nil,从 SideTables 获取 newObj 的 SideTable 赋值给 newTable。两个散列表处理好了后,因为当前是 __weak 变量的创建,处理的是新值,所以下面只给出新值有关的处理代码
// 给新旧散列表加锁
SideTable::lockTwo(oldTable, newTable);
// 通过确保没有弱引用对象具有未初始化的isa,防止弱引用机制和初始化机制之间的死锁。
if (HaveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// 如果这个类完成了+initialize,那最好。如果这个类仍在这个线程上运行+initialize(即+initialize,调用storeWeak,在其自身的实例上),我们可以继续,但是将显示为初始化和尚未初始化作为上述检查,设置 previouslyInitializedClass 以在重试时识别它
previouslyInitializedClass = cls;
goto retry;
}
}
此步骤为确保弱引用对象 newObj 初始化了,首先通过获取 newObj 的 isa 指针获取它的类,然后判断它的类是否初始化了,如果没有,便打开新旧散列表的锁,获取 newObj 的元类发送 +initialize 消息进行初始化。下面是 storeWeak 函数最后一部分:
// Assign new value, if any.
if (HaveNew) {
// 弱引用注册失败便返回 nil
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, CrashIfDeallocating);
// 设置refcount表中的弱引用位
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// 不要在其他地方设置 *location,否则会有冲突。
*location = (id)newObj;
}
// 打开新旧散列表的锁
SideTable::unlockTwo(oldTable, newTable);
// 弱引用处理完毕,返回新值
return (id)newObj;
首先,通过 weak_register_no_lock 函数将 __weak 变量对 newObj 的弱引用注册到 newObj的散列表的弱引用表中,如果注册成功则设置 newObj 的refcount表中的 __weak 变量对其的引用为弱引用,然后将新值赋给 __weak 变量。最后,所有操作完成了,打开新旧散列表的锁,返回新值赋给 __weak 变量。
弱引用注册
__weak 变量引用对象时,需要将 __weak 变量的弱引用注册到被引用对象的弱引用表中,这一操作便由 weak_register_no_lock 函数完成。此函数的实现我们分两部分程呈现:
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
// 确保引用的对象是可用的
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
上部分是为了确保弱引用的对象 referent(newObj对象)支持被弱引用。首先判断引用对象 referent 的 isa 中是否有自定义 retain 和 release 实现,如果没有,则调用 rootIsDeallocating() 函数检查 referent 是否在析构(即是否被释放)。
如果referent 的 isa 中有自定义 retain 和 release 实现,首先会获取 referent 中 SEL_allowsWeakReference 方法的实现,如果获取的是 _objc_msgForward 消息转发函数,那么表明该引用对象不支持弱引用。反之,便发送 SEL_allowsWeakReference 消息去判断该对象是否支持弱引用,如果支持则表示 referent 引用对象不在析构。
如果该引用对象在析构并且 crashIfDeallocating(控制引用对象析构是否需crash)为true,则crash。如果 crashIfDeallocating 为 false,则返回 nil 表示注册弱引用失败。
// now remember it and where it is being stored
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_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
这下半部分就是将 __weak 变量的弱引用指针存储到被引用对象 newObj 的弱引用表中,完成注册。首先通过 weak_entry_for_referent 函数去查找 newObj 对应的 SideTable 的 weak_table 表中的对应 newObj 的弱引用条目 entry。如果不存在 entry,则用 newobj 和 __weak变量指针生成一个新的弱引用条目 new_entry。接下来执行 weak_grow_maybe 函数看 weak_table 是否需要扩容。
weak_grow_maybe 和 weak_resize
#define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
static void weak_grow_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);
// Grow if at least 3/4 full.
// 如果当前条目数已满容量的 3/4 则允许扩容
// 如果是初次的话扩容 64,之后以 2 倍增加
if (weak_table->num_entries >= old_size * 3 / 4) {
weak_resize(weak_table, old_size ? old_size*2 : 64);
}
}
如果 newObj 是第一次被引用,那么其对应的 weak_table 的容量 mask 应为 0,则 old_size = 0, weak_table 的弱引用条目总数自然也为 0。满足扩容条件,因此初次扩容为 64,执行 weak_resize(weak_table, 64)。
weak_resize 是对 weak_table 扩容的函数,其实现如下:
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;
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
if (old_entries) {
weak_entry_t *entry;
weak_entry_t *end = old_entries + old_size;
for (entry = old_entries; entry < end; entry++) {
if (entry->referent) {
weak_entry_insert(weak_table, entry);
}
}
free(old_entries);
}
}
创建一个 weak_entry_t 实例 old_entries 保存 weak_table 弱引用表中的弱引用条目列表,然后创建一个 64 格的新弱引用条目列表,接着更新设置 weak_table 的容量大小、弱引用条目列表、最大哈希位移数、条目总数。最后,如果旧弱引用条目列表 old_entries 存在数据,则将旧条目列表的数据插入 weak_table 新扩容的条目列表中并释放旧条目列表。
扩容完后,便开始将新创建的条目插入 weak_table 的条目列表 weak_entries 中。
weak_entry_insert
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);
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_table->num_entries++;
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}
通过 hash_pointer(new_entry->referent) 和 weak_table->mask 的与运算决定新弱引用条目在 weak_entries 的初始下标,如果 weak_entries 中该下标中没被用,则将 new_entry 存放在此处,weak_table 的 num_entries 自增长 1。
如果该初始下标中已被存放了条目,则循环将 hash_pointer() 计算的 hash值 + 1 再次与 weak_table->mask 进行“与运算”并且哈希位移数自增加 1,如果没找到可存储的位置则会执行 bad_weak_table 报 “This may be a runtime bug or a memory error somewhere else.” 错误。
如果找到可用的位置,则 new_entry 将存放到 weak_entries 中。最后,如果哈希位移数大于 weak_table 中存储的最大哈希位移数,则更新 weak_table 中的 max_hash_displacement 值为 hash_displacement。 到此处,弱引用的注册也就完成了。
append_referrer
如果 newObj 是一个被其他变量弱引用的对象,那么能通过 weak_entry_for_referent 函数找到 newObj 对应的弱引用条目。将 __weak 变量的指针保存到弱引用条目的引用指针数组中完成注册,我们来看看这个过程是怎么样的
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {
// Try to insert inline.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// 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;
}
上部分,判断弱引用条目中存放的引用指针数超过了 inline_referrers 数组的容量。如果没有超过的话(有可能容量已满),则遍历 inline_referrers 找到空位置存放 new_referrer。如果 inline_referrers 容量已满,改用 entry 的 referrers 列表存放引用指针。首先,将 inline_referrers 中存放的引用指针加到 referrers 中,更新设置 num_refs、out_of_line_ness(是否超出了inline_referrers数组的容量)、mask、max_hash_displacement。接下来就进入下部分
assert(entry->out_of_line());
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
如果 entry 的引用指针数达到了存放容量的 3/4,那么对 new_referrer 进行扩容并且插入 new_referrer。
grow_refs_and_insert
#define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
static void grow_refs_and_insert(weak_entry_t *entry,
objc_object **new_referrer)
{
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;
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
append_referrer(entry, new_referrer);
if (old_refs) free(old_refs);
}
这个函数和 weakTable 的扩容函数 weak_resize 一样,首先通过 old_size 计算出扩容的大小 new_size,然后创建一个 weak_referrer_t 实例 old_refs 存放 entry 中 referrers 列表的引用指针,然后对 entry 的 referrers 进行初始化扩容。最后,如果 old_refs 有数据(即原 entry 存在的引用指针),将引用指针通过 append_referrer 插入到扩容后的 referrers 中,此步骤为递归调用。插入的主要代码便是 append_referrer 的最后一部分,如下
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
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;
}
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
上方代码和 weakTable 的 weak_entry_insert 函数实现原理一样,便不再讲述。从上面分析下来,可见 weakTable 的weak_entries 和 entry 的referrers 一样是一个可以自动扩容的数组,而 entry 的inline_referrers 是一个不可扩容的数组。
__weak 变量销毁
__weak 变量销毁会调用 objc_destroyWeak 这个函数
void
objc_destroyWeak(id *location)
{
(void)storeWeak
(location, nil);
}
其中实现也是通过 storeWeak 函数将 __weak 变量置为nil,下面只显示相关代码
template
static id
storeWeak(id *location, objc_object *newObj)
{
assert(HaveOld || HaveNew);
if (!HaveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
retry:
if (HaveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (HaveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo(oldTable, newTable);
if (HaveOld && *location != oldObj) {
SideTable::unlockTwo(oldTable, newTable);
goto retry;
}
// Clean up old value, if any.
if (HaveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
SideTable::unlockTwo(oldTable, newTable);
return (id)newObj;
}
从 objc_destroyWeak 函数传入的 HaveOld = true、HaveNew = false、CrashIfDeallocating = false,首先将 __weak 变量的内存指针指向 oldObj,将当前值变成旧值,对应的从 SideTables 取出对应的 SideTable 为 oldTable,将 newTable 赋值为 nil。然后,将 oldTable 和 newTable 加锁,如果旧值存在并且旧值 __weak 变量内存地址中的值和旧值不相等的话,那么需要重新执行第一步骤以保证销毁工作进行。接下来就是注销 oldObj 对应的 weak_table 中 __weak 变量的弱引用。最后,解开 oldTable 和 newTable 的锁,返回 nil,将 __weak 变量置 nil。
weak_unregister_no_lock
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if (!referent) return;
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.
}
首先,通过 weak_entry_for_referent 找到 weak_table 中的弱引用条目 entry,然后通过 remove_referrer 函数从 entry 的引用指针列表中删除 __weak变量指针。如果 entry 中没有引用指针了,那么便会执行 weak_entry_remove 从弱引用表 weak_table 中删除该弱引用条目。
remove_referrer
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--;
}
如果,entry 的引用指针数不超过 inline_referrers 的容量,那么遍历 inline_referrers 找到引用指针的位置并置为nil。如果引用指针数不超过 inline_referrers 的容量,那么便得去 referrers 中找到引用指针置为nil并将 referrers 的长度减一。如果找不到便会调用 objc_weak_error()
weak_entry_remove
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// remove entry
if (entry->out_of_line()) free(entry->referrers);
bzero(entry, sizeof(*entry));
weak_table->num_entries--;
weak_compact_maybe(weak_table);
}
此函数将弱引用条目 entry 从 weak_table 中删除,然后通过 weak_compact_maybe 去检查是否需要缩小 weak_table 的容量。
weak_compact_maybe
#define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
static void weak_compact_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);
// Shrink if larger than 1024 buckets and at most 1/16 full.
if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
weak_resize(weak_table, old_size / 8);
// leaves new table no more than 1/2 full
}
}
如果达到缩小容量大小的要求,便通过 weak_resize 函数调整容量为原来的 1/8。