Reference Counting

说明

Clang 13 documentation

LLVM 的关于 ARC 的说明,非常长。

引用计数原理

Objective-C 引用计数原理

引用计数如何存储:

  1. 如果是 Tagged Pointer ,会直接使用其指针值作为引用计数返回;
  2. 如果是 64位和 Objective-C 2.0 ,就会使用 isa 的部分空间来存储引用计数;
  3. 如果 isa 部分空间不够存储引用计数,或者不是 64位和 Objective-C 2.0 ,就会使用散列表和 SideTable 来存储。

isa 指针中变量对应的含义

获取引用计数:

inline uintptr_t objc_object::rootRetainCount() { assert(!UseGC); // 1. 如果是 Tagged Pointer ,则直接返回指针地址 if (isTaggedPointer()) return (uintptr_t)this; sidetable_lock(); isa_t bits = LoadExclusive(&isa.bits); if (bits.indexed) { // 2. 如果有经过优化,则 1 + bits.extra_rc ; uintptr_t rc = 1 + bits.extra_rc; // 3. 如果有使用 SideTable ,则加上 SideTable 的计数; if (bits.has_sidetable_rc) { rc += sidetable_getExtraRC_nolock(); } sidetable_unlock(); return rc; } sidetable_unlock(); return sidetable_retainCount(); }

sidetable_retainCount() 则负责从 SideTable 中获取引用计数:

uintptr_t objc_object::sidetable_retainCount() { // 1. 根据指针地址获取对应的 SideTable SideTable *table = SideTable::tableForPointer(this); size_t refcnt_result = 1; spinlock_lock(&table->slock); // 2. 获取对象对应的 RefcountMap ; RefcountMap::iterator it = table->refcnts.find(this); if (it != table->refcnts.end()) { // 右移二位, SIDE_TABLE_RC_PINNED 可用于判断是否溢出。 refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT; } spinlock_unlock(&table->slock); return refcnt_result; }

如官方文档所说的,不要使用 retainCount 方法,它不能真实表达对象所对应的引用计数,以及 Clang 一些优化也会对其造成影响。

Apple Developer Documentation

修改引用计数:

  1. retainrelease
  2. allocnewcopymutableCopy
  3. autorelease

黑箱中的 retainrelease

黑箱中的 retain 和 release

retain

原文把 rootRetain 方法拆散了,方便解析,下面是 rootRetain 的全部实现,易于查看整个流程:

id objc_object::rootRetain(bool tryRetain, bool handleOverflow) { if (isTaggedPointer()) return (id)this; bool sideTableLocked = false; bool transcribeToSideTable = false; isa_t oldisa; isa_t newisa; do { transcribeToSideTable = false; // 加载 isa 的值 oldisa = LoadExclusive(&isa.bits); newisa = oldisa; // 如果不支持 nonpointer ,即不用 isa 本身来存储引用计数,则直接走 SideTable 流程 if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; else return sidetable_retain(); } // 如果 isa 在 dealloc 流程中,则直接返回 nil if (slowpath(tryRetain && newisa.deallocating)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); return nil; } // uintptr_t carry; // 将 isa 的值加 1 newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // 引用计数超出 extra_rc 限制,改用 SideTable if (slowpath(carry)) { // handleOverflow 为 false if (!handleOverflow) { ClearExclusive(&isa.bits); // handleOverflow 改为 true ,从走 retain return rootRetain_overflow(tryRetain); } // 把 isa 的 extra_rc 恢复为一半,和做好准备把另外一半拷贝到 SideTable if (!tryRetain && !sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; // 把 extra_rc 恢复为一半 newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } // StoreExclusive 更新 isa 的值 } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); // 如果说 isa 有溢出, 把一半的引用计数拷贝到 SideTable if (slowpath(transcribeToSideTable)) { sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; }
bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc) { SideTable& table = SideTables()[this]; size_t& refcntStorage = table.refcnts[this]; size_t oldRefcnt = refcntStorage; // 溢出,直接返回 true 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 refcntStorage = SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK); return true; } else { // 没有溢出,新值赋给 refcntStorage refcntStorage = newRefcnt; return false; } }

可以看到 retain 时会动态结合 isaextra_rcSideTable 来存储引用计数:

  1. extra_rc 不需要查找,速度会更快,所以优先使用 extra_rc 管理引用计数;
  2. 如果 extra_rc 溢出,则把 extra_rc 的一半拷贝到 SideTable 中, SideTable 并没有直接参与引用计数管理;
  3. 如果引用计数为 1 , extra_rc 为 0 ,保存的是额外的引用计数。

release

bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) { if (isTaggedPointer()) return false; bool sideTableLocked = false; isa_t oldisa; isa_t newisa; retry: do { oldisa = LoadExclusive(&isa.bits); newisa = oldisa; // 如果不支持 nonpointer ,即不用 isa 本身来存储引用计数,则直接走 SideTable 流程 if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); if (sideTableLocked) sidetable_unlock(); return sidetable_release(performDealloc); } uintptr_t carry; // extra_rc 减 1 newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // 如果不够,则走 SideTable 流程 if (slowpath(carry)) { goto underflow; } // 调用 StoreReleaseExclusive 方法保存新的引用计数 } while (slowpath(!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(sideTableLocked)) sidetable_unlock(); return false; underflow: newisa = oldisa; // 判断是否有使用 SideTable if (slowpath(newisa.has_sidetable_rc)) { // 将 handleUnderflow 设为 true ,递归调用 if (!handleUnderflow) { ClearExclusive(&isa.bits); return rootRelease_underflow(performDealloc); } if (!sideTableLocked) { ClearExclusive(&isa.bits); sidetable_lock(); sideTableLocked = true; goto retry; } // 从 SideTable 中获取 extra_rc 最大值的一半,即 RC_HALF size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); if (borrowed > 0) { // 将一半引用计数放到 extra_rc 中 newisa.extra_rc = borrowed - 1; bool stored = StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits); if (!stored) { // 如果存放到 extra_rc 中失败,重新加载 isa 和尝试存放 isa_t oldisa2 = LoadExclusive(&isa.bits); isa_t newisa2 = oldisa2; if (newisa2.nonpointer) { uintptr_t overflow; newisa2.bits = addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow); if (!overflow) { stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, newisa2.bits); } } } // 如果还是失败,则把引用计数放回 SideTable if (!stored) { sidetable_addExtraRC_nolock(borrowed); goto retry; } sidetable_unlock(); return false; } else { // SideTable 为空,执行 dealloc ,不需要执行任何处理 } } if (slowpath(newisa.deallocating)) { // 对象正在释放 ClearExclusive(&isa.bits); if (sideTableLocked) sidetable_unlock(); return overrelease_error(); } // 设置 deallocating 标志位 newisa.deallocating = true; if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; if (slowpath(sideTableLocked)) sidetable_unlock(); __sync_synchronize(); if (performDealloc) { // 通过 objc_msgSend 直接执行 dealloc ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); } return true; }

release 的处理流程和 retain 类似,多了一个 dealloc 的执行流程。

引用计数与 weak

内存管理(四)引用计数与weak

源码解析在上面已经有提及,所以这里贴上这篇的文章里的图就好了。