SideTables 是一个全局的 hash 表,用来存储对象多余的引用计数,以及弱引用表
一、 SideTables
- SideTables 是一个全局的 hash 表, 其中包含了一个数量为64的数组,数组中的存储的为SideTable结构体,其中通过对象内存地址作为hash表的key
enum { StripeCount = 64 };
PaddedT array[StripeCount];
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
- SideTable中包含一个 c++ Map
RefcountMap refcnts
用来对象存储额外的引用计数,一个结构体weak_table_t weak_table
用来存储对象的弱引用数据
RefcountMap refcnts
中通过一个size_t
(64位系统中占用64位)来保存引用计数,其中1位用来存储固定标志位,在溢出的时候使用,一位表示正在释放中,一位表示是否有弱引用,其余位表示实际的引用计数
-
RefcountMap refcnts
是一个C++的对象,内部包含了一个迭代器 - 其中以
DisguisedPtr<objc_object>
对象指针为key,size_t
为value保存对象引用计数 - 将key、value通过
std::pair
打包以后,放入迭代器中,所以取出值之后,.first代表key,.second代表value
在上一篇中、retain release
方法会使用到SideTables来存放引用计数
// Move some retain counts to the side table from the isa field.
// Returns true if the object is now pinned.
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];
size_t& refcntStorage = table.refcnts[this];
size_t oldRefcnt = refcntStorage;
// isa-side bits should not be set here
assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
uintptr_t carry;
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
if (carry) {
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else {
refcntStorage = newRefcnt;
return false;
}
}
// Move some retain counts from the side table to the isa field.
// Returns the actual count subtracted, which may be less than the request.
size_t
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end() || it->second == 0) {
// Side table retain count is zero. Can't borrow.
return 0;
}
size_t oldRefcnt = it->second;
// isa-side bits should not be set here
assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
assert(oldRefcnt > newRefcnt); // shouldn't underflow
it->second = newRefcnt;
return delta_rc;
}
- 第一个方法用来将多余的引用计数保存到SideTables中, 第二个方法用来取出引用计数
- 其中都用到了SideTables中的
RefcountMap refcnts
,但是两个方法的实现并不相同
// 第一种方式
SideTable& table = SideTables()[this];
size_t& refcntStorage = table.refcnts[this];
size_t oldRefcnt = refcntStorage;
// 第二种方式
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end() || it->second == 0) {
// Side table retain count is zero. Can't borrow.
return 0;
}
size_t oldRefcnt = it->second;
- 其实第一种方式是通过重载
[]
操作符来实现查找的,但是添加的时候需要判断如果不存在记录的话,需要新增一条数据保存,所以使用了两种不同的方法
// 通过操作符重载实现 查找、新增插入
value_type& FindAndConstruct(const KeyT &Key) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket))
return *TheBucket;
return *InsertIntoBucket(Key, ValueT(), TheBucket);
}
ValueT &operator[](const KeyT &Key) {
return FindAndConstruct(Key).second;
}
- 两个方法都是获取到
RefcountMap refcnts
进行引用计数的增加和减少
weak_table_t 弱引用表
- 全局弱引用表 weak_table_t
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
- 弱引用表的内部结构
struct weak_entry_t {
DisguisedPtr<objc_object> 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 {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};
1️⃣. 其中主要包含了两个属性DisguisedPtr<objc_object> referent
对象指针,还有一个容器类保存所以只需这个对象的弱引用
2️⃣. 共用体中包含两种结构体,当弱引用数量少于4的时候,使用数据结构来存储,当超过4个的时候使用hash表进行存储,out_of_line_ness 默认为 ob00,当弱引用数量大于4的时候,设置为 REFERRERS_OUT_OF_LINE ob10,通过判断out_of_line_ness来决定用什么方式存储
3️⃣. weak_referrer_t *referrers
是一个二级指针实现的hash表
二、 weak 的实现原理
{
id obj1 = [[NSObject alloc] init];
id __weak obj2 = obj1;
}
当我们创建弱引用对象时,系统会编译成以下代码
id obj2;
objc_initWeak(&obj2, obj1);
objc_destroyWeak(&obj2);
通过 objc_initWeak(&obj2, obj1);
创建weak引用,在对象作用域结束时,使用objc_destroyWeak(&obj2);
来释放引用
- 创建弱引用
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
void
objc_destroyWeak(id *location)
{
(void)storeWeak<true/*old*/, false/*new*/, false/*crash*/>
(location, nil);
}
两个方法最终都调用了storeWeak
,但是两个方法调用参数不同。
init方法中,将要引用的对象作为参数传递,并且HaveOld参数为false,HaveNew参数为true
而destroy方法中,将nil作为新对象传入,并且HaveOld参数为true,HaveNew参数为false
HaveOld 代表是否有旧的引用,如果为true,则代表有旧的引用需要释放
HaveNew 代表是否有新的引用,如果为true,则代表要存储新的引用
template <bool HaveOld, bool HaveNew, bool 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 *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;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
// 如果有新值,判断新值所属的类是否已经初始化,如果没有初始化,则先执行初始化,防止+initialize内部调用storeWeak产生死锁
if (HaveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(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.
// 如果存储成功则设置SideTable中弱引用标志位
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;
}
以上就是 store_weak 这个函数的实现,它主要做了以下几件事:
- 分别获取新旧值的散列表指针
- 如果有旧值就调用 weak_unregister_no_lock 函数清除旧值
- 如果有新值就调用 weak_register_no_lock 函数分配新值
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;
}
}
// 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_register_no_lock 用来保存弱引用信息,具体实现如下:
- 判断对象是否可以弱引用,是否正在释放。
- 查询weak_table_t, 判断对象是否已经保存有相关联的弱引用信息
- 如果已经有相关弱引用信息,则调用
append_referrer
方法添加进现在的weak_entry_t结构中,如果没有相关联信息,则创建weak_entry_t 节点,并且插入到weak_table_t弱引用表中。
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_unregister_no_lock 用来移除弱引用信息,具体实现如下:
- 查询弱引用表中是否保存有相关联的弱引用信息
- 如果有,则调用
remove_referrer
方法移除相关联的弱引用信息 - 移除相关联弱引用信息之后,判断存储数组是否为空,如果为空,则调用
weak_entry_remove
移除weak_entry_t节点
在dealloc中,释放对象时会调用一下方法
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
此方法用来移除SideTables中对象相关联的数据
- 判断是否有弱引用,如果有调用
weak_clear_no_lock
方法移除所有相关联弱引用 - 判断是否有额外的引用计数存储在SideTables中,如果有则调用
table.refcnts.erase(this);
清除
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) {
*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_remove(weak_table, entry);
}
weak_clear_no_lock 方法用来清空对象相关联的所有弱引用数据,具体实现如下:
- 判断对象是否有相关联的弱引用数据,如果没有则直接return
- 通过out_of_line判断实际存储结构,如果为0b10则用的是指针数组,否则是长度为4的数组结构
- 遍历数组,将所有对象设置为nil
- 调用
weak_entry_remove(weak_table, entry);
移除节点