本章提纲:
1.散列表SideTable
2.弱引用表
上一篇文章最后看到关于Retain
的操作里面有关于SideTable
相关的内容,这一篇主要来研究一下SideTable
。我们接着Retain
的源码操作来看。
1.散列表SideTable
- rootRetain详解
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
//判断是小对象类型 不进行retain 直接返回
if (slowpath(isTaggedPointer())) return (id)this;
//默认不开启散列表
bool sideTableLocked = false;
//默认不将引用计数转换到散列表
bool transcribeToSideTable = false;
//旧的isa
isa_t oldisa;
//新的isa
isa_t newisa;
//原子加载isa
oldisa = LoadExclusive(&isa.bits);
// 支持SUPPORT_NONPOINTER_ISA的情况下的优化
if (variant == RRVariant::FastOrMsgSend) {
// These checks are only meaningful for objc_retain()
// They are here so that we avoid a re-load of the isa.
if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
ClearExclusive(&isa.bits);
if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
return swiftRetain.load(memory_order_relaxed)((id)this);
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
}
if (slowpath(!oldisa.nonpointer)) {
// a Class is a Class forever, so we can perform this check once
// outside of the CAS loop
if (oldisa.getDecodedClass(false)->isMetaClass()) {
ClearExclusive(&isa.bits);
return (id)this;
}
}
//循环操作
do {
//默认不使用散列表计数 使用标志位计数
transcribeToSideTable = false;
//新值赋值
newisa = oldisa;
//不支持nonpointer
if (slowpath(!newisa.nonpointer)) {
//清除isa的bits
ClearExclusive(&isa.bits);
//传进来的tryRetain 为YES 调用sidetable_tryRetain 散列表尝试调用Retain
//如果对象正在销毁 返回空
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain(sideTableLocked); //引用计数为+1
}
// don't check newisa.fast_rr; we already called any RR overrides
//isa处于一个正在释放中的状态 那么执行retain都是无效的
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
if (slowpath(tryRetain)) {
return nil;
} else {
return (id)this;
}
}
//引用计数是否溢出标志位
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
//如果引用计数标志位是溢出
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
//variant 为Fast或者FastOrMsgSend的状态 去执行rootRetain_overflow
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
//去尝试执行rootRetain_overflow 这个方法实际上调用的是 rootRetain 但是RRVariant 传的又是Full
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
//开启散列表进行引用计数
transcribeToSideTable = true;
//因为此时已经满了 将extra_rc置为原来的一半,也就是最高位置为0
//这样下次使用引用计数的时候优先去使用isa中的引用计数标志位来记录 而不是直接使用引用计数表
//引用计数表要通过索引去查询 比直接处理isa的效率要低
newisa.extra_rc = RC_HALF;
//散列表使用引用计数的标志位 为true
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
//把一半的引用计数存到side table 使引用计数的标志位空出来一半 也相当于是一半扩容了其实
if (variant == RRVariant::Full) {
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!transcribeToSideTable);
ASSERT(!sideTableLocked);
}
return (id)this;
}
加了一部分注释,这个引用计数的操作主要有三个部分:
1、如果是小对象类型isTaggedPointer
那么什么都不做,直接返回;
2、非小对象类型,判断isa的状态,如果处于Deallocating
那么也啥都不做,做了也没用反正;
3、非小对象类型,并且不处于Deallocating
又分为两种情况
- 3.1 isa表示引用计数的位没有满,直接进行rc+1返回;
- 3.2 如果引用计数满了,那么要开始使用引用计数表进行协助计数,isa中的引用计数减半
RC_HALF
,也就是最高位置为0,然后把减半的引用计数存到引用计数表中,has_sidetable_rc
置为true
就是开启了引用计数表进行计数。
之所以这么做,是为了下一次再去进行引用计数+1的计算,优先去在isa
进行处理,有一半的引用计数被存到了SideTable
中,那么被减半的isa
中存的引用计数还可以再存一半才满,以此循环,查表的效率要比直接使用isa
要低,所以优先去操作isa
中的计数。
- sidetable_tryRetain
bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
//获取相应的table
SideTable& table = SideTables()[this];
// NO SPINLOCK HERE
// _objc_rootTryRetain() is called exclusively by _objc_loadWeak(),
// which already acquired the lock on our behalf.
// fixme can't do this efficiently with os_lock_handoff_s
// if (table.slock == 0) {
// _objc_fatal("Do not call -_tryRetain.");
// }
bool result = true;
//try_emplace 如果该对象在buckets中不存在会插入一个新值,如果存在会返回这个值
//try_emplace 有两个返回值 第一个返回值是遍历器,第二个返回值代表是否存在
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE);
auto &refcnt = it.first->second;
if (it.second) {
// there was no entry //没有相关的entry 是第一次插入到buckets中
} else if (refcnt & SIDE_TABLE_DEALLOCATING) {
//当前对象正在销毁中
result = false;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
//没有溢出 并且引用计数+1
refcnt += SIDE_TABLE_RC_ONE;
}
return result;
}
这部分去查引用计数表,如果对象已经存在并且没有引用计数没有达到最大值,进行引用计数+1,如果不存在那么为这个对象创建一个引用计数表,插入到相关的map中。
- sidetable_retain
id
objc_object::sidetable_retain(bool locked)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
//拿到对应的SideTable
SideTable& table = SideTables()[this];
if (!locked) table.lock();
//获取RefcountMap 的大小
size_t& refcntStorage = table.refcnts[this];
//RefcountMap没有满 那么进行 引用计数位+1的操作
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
RefcountMap 的大小没有满,进行+1操作。
- rootRetain_overflow
objc_object::rootRetain_overflow(bool tryRetain)
{
return rootRetain(tryRetain, RRVariant::Full);
}
实际上调用的还是rootRetain
。
- SideTable的结构
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);
};
这个结构中包含一个spinlock_t
互斥锁;
一个RefcountMap
的引用计数表;
还一个weak_table_t
弱引用表;
通过上面👆🏻的代码,来看下SideTable
的读取,是通过SideTables()[this]
方法进行获取的,把对象的指针传进去,拿到对应的SideTable,而SideTables
我们进一步查看:
static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}
ExplicitInit
的作用是生成一个模板类型Type的实例。而StripedMap<SideTable>& SideTables()
实际上返回了全局静态StripedMap<SideTable>
。简单看下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];
//哈希计算取出相应的value值
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
public:
//运算符`[]`重载,所以SideTable[this] = SideTable[indexForPointer(this)].value
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}
//此处省略若干行
}
这个StripedMap
在真机下StripeCount
是8,模拟器下StripeCount
是64。
方法indexForPointer
通过对象的地址,来确定是在array中的第几个元素,进而拿到哈希值。
而关键的地方在于,这个类对[]
运算符进行了重载,前面取出对应的SideTable
用的方法是SideTables()[this]
,它的翻译过来的实际操作是:SideTables().array[indexForPointer(this)].value
,就是先通过indexForPointer
计算出位置,在通过array[index]取出值,就是对应的SideTable
。
2.弱引用表
上面的SideTable
结构中包括weak_table_t
,它的结构如下:
/**
* 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;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
这是全局的弱引用表,对象的id作为key,weak_entry_t
是对应的值。
-
weak_entries
哈希数组,用来存储弱引用对象的相关信息weak_entry_t
。 -
num_entries
数组个数 -
mask
哈希数组长度,会参与哈希计算。 -
max_hash_displacement
最大哈希偏移值。
weak_entry_t结构
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];
};
};
//省略部分代码
};
-
referent
被弱引用的对象 -
union
联合体中,两个结构体共用一块内存,动态数组和非动态数组只用一种。
通过上面的结构可以了解到这个弱引用表是在SideTables
->SideTable
->weak_table_t
最终找到被弱引用者的引用weak_entry_t
这样的层级结构。
举个例子:
比如,b,c,d分别弱引用了a,那么通过一系列的查找,a的弱引用表里会存了b,c,d这三个对象的地址,b,c,d这三个对象的地址对应的就是weak_entry_t这样一个层级。
所以通过以上的分析,我们也能够明白,为什么弱引用对象不会增加对象的引用计数,因为从本质上这就是两个表,当进行弱引用时,不会去操作对象的引用计数表,而是操作对象的弱引用表。
附一张结构图:
下面接着来看弱引用相关的源码,看看具体的对弱引用对象的操作,以及弱引用表中的对象是如何置为nil
的。
2.1弱引用相关源码
前面看相关SideTable
的内容都是在文件objc-weak.h
中,我们搜索下这个文件,可以发现有相关的.m,于是可以在.m中可以看到一些操作的具体实现。我们先来看看weak
是如何插入到weak
表中的。
- weak_entry_insert
/**
* Add new_entry to the object's table of weak references.
* Does not check whether the referent is already in the table.
* 添加新的entry到对象的weak引用表里
* 不会检查这个引用是不是已经在这个表里
*/
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
//拿到weak_table中的所有weak_entries
weak_entry_t *weak_entries = weak_table->weak_entries;
ASSERT(weak_entries != nil);
//获取new_entry的index
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
//偏移量为0
size_t hash_displacement = 0;
//不为空 说明该index对应的位置已经有弱引用对象
while (weak_entries[index].referent != nil) {
//往下挪一个index的位置
index = (index+1) & weak_table->mask;
//index又找回到了起始位置 说明找了一圈都没找到 调用bad_weak_table 是一个异常
if (index == begin) bad_weak_table(weak_entries);
//偏移量++
hash_displacement++;
}
//找到了没存entry为空的位置 在相应的index写入新的new_entry的地址
weak_entries[index] = *new_entry;
//num_entries数增加1
weak_table->num_entries++;
//如果最大的偏移值比原来的大 那么更新最大偏移值到weak_table中
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}
插入新的entry的流程
:
1.获取到对象的弱引用表;
2.通过new_entry进行哈希计算,拿到第一次应该插入到weak表中的初始位置,用临时变量begin
记录下,并且初始化index。
3.开始进行遍历,如果在weak_entries
该index位置中的内容不为空,说明哈希冲突了,把index进行后移,直到找到第一个为空的位置,跳出循环。
- 那么在循环中,如果index转了一圈又回到了begin初始位置,说明没找到空位置可以插入,此时会调用
bad_weak_table
进行报错。 - 在循环中index每后移一次,哈希冲突都会进行+1的操作记录一次,也就是最大偏移量会增加一次。
4.找到了为空的位置,那么把新值new_entry的指针插入到这个位置中,更新num_entries
,进行比较是否需要更新最大偏移量的值。
- storeWeak
//存储weak
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;
// 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:
//有旧值 取出旧值给oldTable 不存在oldTable置为nil
if (haveOld) {
//根据传入的地址获取到旧值
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
//有新值 取出新值给newTable 没新值newTable置为nil
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
//加锁
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
//前边刚赋值过旧值 它的location应该和oldObj的一致,如果不一致说明别的线程修改过 那么重新赋值oldObj 重新记录
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.
//有新值
if (haveNew && newObj) {
Class cls = newObj->getIsa();
//如果isa还没有初始化 那么先初始化isa 然后重试
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.
// 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.
// 如果该类还没有初始化完成,例如在 +initialize 中调用了 storeWeak 方法,
// 也就是会进入这里面,进而设置 previouslyInitializedClass 以在重试时识别它
// 这里记录一下previouslyInitializedClass, 防止if分支再次进入 造成死循环
previouslyInitializedClass = cls;
goto retry;
}
}
// Clean up old value, if any. 清除旧值 如果该weak指针 弱引用过别的对象 要从旧对象的weak表里清除该引用
if (haveOld) {
//调用清除方法
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any. 注册新值到表里
if (haveNew) {
newObj = (objc_object *)
// (1) 调用weak_register_no_lock方法,将weak ptr的地址记录到newObj对应的weak_entry_t中
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.
// (2) 更新newObj的isa的weakly_referenced bit标志位
if (!newObj->isTaggedPointerOrNil()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
// (3)*location 赋值,也就是将weak ptr直接指向了newObj。可以看到,这里并没有将newObj的引用计数+1
// 将weak ptr指向object
*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;
}
该方法比较长核心部分有三个:
1、对旧值的处理,如果weak指针引用过旧值,那么要先在旧值的weak表中清除掉这个weak指针的地址。
2、新值的插入。新值的插入在清除完毕旧值的基础上进行该weak指针在新对象的weak表中插入。
3、更新weakly_referenced
标志位,更新location
到新对象上。
其中包括清除方法weak_unregister_no_lock
和注册weak_register_no_lock
方法:
-
weak_unregister_no_lock
加了详细的注释。整个移除的过程很严谨,会去逐一判断,从inline固定的四个引用计数长度判断,再去判断out_line动态数组是不是使用了,再到判断空表是不是需要表移除。
weak_register_no_lock
/**
* 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.
*/
//注册一个新的objct,weak pointer对,如果objct的entry不存在那么会创建一个新的weak
// weak_table 目标被弱引用对象所存储的表
// referent_id 被所引用的对象
// referrer_id 要被添加的弱引用指针
// crashIfDeallocating 如果对象正在被释放时是否崩溃
//weak_register_no_lock(&newTable->weak_table, (id)newObj, location,crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
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;
// ensure that the referenced object is viable
// 确保被引用的对象可用没有在析构,同时应该支持weak引用
if (deallocatingOptions == ReturnNilIfDeallocating ||
deallocatingOptions == CrashIfDeallocating) {
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
// Use lookUpImpOrForward so we can avoid the assert in
// class_getInstanceMethod, since we intentionally make this
// callout with the lock held.
// referent是否支持weak引用
auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
referent->getIsa());
// 如果referent不能够被weak引用,则直接返回nil
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
// 调用referent的SEL_allowsWeakReference方法来判断是否正在被释放
deallocating =
! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
}
//deallocating正在析构对象 不能引用 判断是不是需要crash
if (deallocating) {
if (deallocatingOptions == 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;
// 在 weak_table中找到referent对应的weak_entry,并将referrer加入到weak_entry中
// 如果能找到weak_entry,则将referrer插入到weak_entry中
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
//找不到 创建一个referrer的entry
weak_entry_t new_entry(referent, referrer);
//判断是不是需要扩容
weak_grow_maybe(weak_table);
//插入新创建的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;
}
插入新的weak指针到相应的entry中时,首先要去判断要进行插入的weak指针的引用对象有没有正在析构,是否可以被弱引用,如果都没有那么开始进行插入。
先判断weak_table中是不是已经存在该对象的弱引用了,如果已经存在了entry(说明之前就被弱引用过了),那么直接调用append_referrer
把new_referrer插入到referrers
中去。
如果不存在entry(也就是第一次被弱引用),那么使用参数referrer创建一个新的entry
,判断是不是需要扩容,将新的entry插入到相应的weak_table
中。
这其中使用到方法:
weak_entry_for_referent
,根据传入的地址查询并返回相关对象的entry
append_referrer
,这里有动态数组四分之三扩容的判断
weak_grow_maybe
,这个地方是扩容处理,进行weak_resize
方法的调用,同样空间过于浪费也会调用weak_compact_maybe
缩小空间的优化处理。
- weak_clear_no_lock
这个方法weak_clear_no_lock
是weak指针置为nil的关键。通过注释可以看到,这个方法通过dealloc
调用,它的调用过程是object_dispose
->objc_destructInstance
->clearDeallocating
->sidetable_clearDeallocating
或者是clearDeallocating_slow
,而方法sidetable_clearDeallocating
和方法clearDeallocating_slow
中都通过判断是不是有弱引用进而调用weak_clear_no_lock
方法。
我们接下来看看具体的实现:
/**
* Called by dealloc; 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.
*/
//对象释放 清除相关weak指针,移除相关的weak表
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
//拿到被销毁的对象的指针
objc_object *referent = (objc_object *)referent_id;
//从weak_table中拿到相关的entry entry中的referrers有弱引用当前对象的所有weak指针
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 {
//没使用动态数组 没超过四个弱引用 直接去处理inline_referrers
//WEAK_INLINE_COUNT 等于 4
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
//开始遍历
for (size_t i = 0; i < count; ++i) {
//**referrer 存的就是被弱引用的指针地址 指针里指向的内容装的还是一个指针
objc_object **referrer = referrers[i];
// 如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil的原因
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);
}
总结
通过上面的结构分析,源码分析,了解了iOS
内存管理中weak
表的操作,增容,缩容,插入删除等等一系列增删改查的操作,可以了解到,weak表的操作都是一层一层进行的,优先是inline固定长度为4的数组,其次是动态数组。
删除的时候,先去在固定长度中进行查找删除,没找到再去查动态数组,删除成功再去判断表删除完是不是为空了需不要要移除表,需不需要缩容;
增加新的弱引用时亦是如此,先去判断inline中有没有位置,有位置直接插入,没位置了开启动态数组,通过index后移的方法查找没有存东西的位置进行插入,插入完去判断需要扩容嘛。
👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻
整个过程是非常严谨的,层层递进~
👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻
weak表告一段落,看了几天也只是了解些皮毛,以后有机会再了解weak相关知识时希望可以更加的深入了解~