内存管理-弱引用分析
散列表结构分析
bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
bool result = true;
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE);
auto &refcnt = it.first->second;
if (it.second) {
// there was no entry
} else if (refcnt & SIDE_TABLE_DEALLOCATING) {
result = false;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
refcnt += SIDE_TABLE_RC_ONE;
}
return result;
}
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
在 内存管理-retain&realese rootRetain
函数中我们介绍了当 isa
不是 nonpointer
类型的时候就会直接操作散列表,对引用计数进行增加。我们进到 sidetable_tryRetain
函数可以看到 SideTables
,代表有很多张表,SideTables
在底层是 StripedMap
,通过 StripedMap
的结构可以看到,最大可以开 64 张表,如果是真机环境下最大就是 8 张表。而 StripedMap
是对哈希表的上层封装。
struct SideTable {
spinlock_t slock;
RefcountMap refcnts; // 引用计数表
weak_table_t weak_table; // 弱引用表
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
通过查看 SideTable
的结构可以看到 SideTable
中包含了引用计数表跟弱引用表,而上面讲的 SideTables
是多张表的形式就是考虑到性能问题,当所有对象都共用一张表的话因为要考虑到多线程的问题,当对引用计数操作的时候就会对表的加锁和关锁,会比较消耗性能,当使用多张表的时候,系统可以根据一定的算法,对不使用的表进行内存回收,而不是持续占用空间。但是也不能每个对象开一张表,因为开表的内存太大了,对象很多的话就会有很多的内存开辟与回收,也会很消耗性能。所以表的数量要在一个合理的范围内。
id
objc_object::sidetable_retain(bool locked)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
if (!locked) table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
sidetable_retain
函数中,SideTables()[this]
根据 this
获取到当前对象所在的表,size_t& refcntStorage = table.refcnts[this]
根据 this
找到对象在 table
中的存储空间,然后对 refcntStorage
进行加操作。
弱引用表分析
如图我们通过 __weak
对 weakObjc
进行修饰,我们在这里进行断点,通过汇编调试可以看到来到了 objc_initWeak
函数,然后我们通过源码来看一下 objc_initWeak
函数做了哪些操作。
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
// 这里是 c++ 的模板函数
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 *newTable;
retry:
// 这里判断对象是否存在旧的引用,如果第一次来到这里说明没加入到过弱引用表,就会走到 else 里面
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
// 这里判断是否存在新的引用,如果成立,就根据 newObjc 到 SideTables 中找到 newTable
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
// 如果没有有新的弱引用,newTable 为 nil
newTable = nil;
}
// 这里判断对象是否存在旧的引用,如果存在就进行相关的移除.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// 这里判断是新的引用
if (haveNew) {
// 这里判断对象是否是 objc_object 类型,是的话就调用 weak_register_no_lock 函数
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);
callSetWeaklyReferenced((id)newObj);
return (id)newObj;
}
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;
objc_object **referrer = (objc_object **)referrer_id;
if (referent->isTaggedPointerOrNil()) return referent_id;
weak_entry_t *entry;
// 这里根据对象(referent)判断 weak_table 是否存在 entry,如果存在就进行追加,往 entry 的 referrers 中添加 referent
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
// 如果 weak_table没有对应的 entry,就通过 referent 跟 referrer 创建 new_entry
weak_entry_t new_entry(referent, referrer);
// 这里进行内存的判断,并根据规则进行扩容
weak_grow_maybe(weak_table);
// 把新的 new_entry 存入到 weak_table 中
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
总结如下:
- 首先根据对象在
SideTables
中查找对应的SideTable
- 判断是否存在旧的引用,如果存在就进行相关的移除
- 判断是新的引用且判断对象是否是
objc_object
类型,是的话就调用weak_register_no_lock
函数
-
weak_register_no_lock
函数中根据对象(referent)
判断weak_table
是否存在entry
,-
如果存在就进行追加,往
entry
的referrers
中添加referent
-
不存在的话,就通过
referent
跟referrer
进行绑定,创建new_entry
,并且进行内存的判断,并根据规则进行扩容,最后把新的new_entry
存入到weak_table
中
-
通过上图可以看到,首先存在一个全局的 SideTables
,然后 SideTables
中会有不同数量的 SideTable
,而且 SideTable
中存在引用计数表跟弱引用计数表 weak_table
,weak_table
又根据不同的对象对应不同的 weak_entry_t
,而 weak_entry_t
中包含 weak_referrer_t *referrers
,referrers
存储的为 objc_object *
类型 DisguisedPtr<objc_object *> weak_referrer_t
。
关于 weak 引用计数的问题
如图当我们打印 weakObjc
的引用计数的时候,发现等于 2,那么这是什么原因呢?我们通过断点调试来看一下。
通过汇编调试我们可以看到打印 weakObjc
的时候调用了 objc_loadWeak
函数,下面我们就来追踪 objc_loadWeak
函数。
id
objc_loadWeak(id *location)
{
if (!*location) return nil;
return objc_autorelease(objc_loadWeakRetained(location));
}
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// 这里 *location 就是 weakObjc
obj = *location;
if (obj->isTaggedPointerOrNil()) return obj;
// 根据 obj 获取 table
table = &SideTables()[obj];
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
// 这里把 obj 赋值给临时变量 result
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
// Fast case. We know +initialize is complete because
// default-RR can never be set before then.
ASSERT(cls->isInitialized());
// 这里 rootTryRetain 会调用 rootRetain,所以引用计数会被加 1
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {...}
table->unlock();
return result;
// 出了这个作用域空间后 result 会被 release
}
ALWAYS_INLINE bool
objc_object::rootTryRetain()
{
return rootRetain(true, RRVariant::Fast) ? true : false;
}
在 objc_loadWeak
函数中我们可以看到,在这里会调用 rootTryRetain
函数,然后 rootTryRetain
函数调用了 rootRetain
函数,然后就会走 rootRetain
流程,所以引用计数会被加 1,但是出了 objc_loadWeak
函数的时候,result
会被 release
,所以引用计数又会被减 1。