-
ARC是LLVM和Runtime配合的结果。 -
ARC中禁止手动调用retain/release/retainCount/dealloc -
ARC新加了weak、strong属性关键字
一、 retain 源码解析
1.1 rootRetain 核心源码
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
//TaggedPointer 直接返回
if (slowpath(isTaggedPointer())) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
oldisa = LoadExclusive(&isa.bits);
......
do {
transcribeToSideTable = false;
newisa = oldisa;
//纯指针
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
//散列表引用计数 + 1
else return sidetable_retain(sideTableLocked);
}
// don't check newisa.fast_rr; we already called any RR overrides
//非纯指针 nonpointer
//正在释放(为了多线程)不做处理。
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 + 1, RC_ONE 从 extra_rc 的最低位开始+1。相当于extra_rc + 1。加满了标记 carry
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
//所有超载
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
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 减半
newisa.extra_rc = RC_HALF;
//标记有散列表
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
//isa extre_rc满了
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 {
......
}
return (id)this;
}
- 判断是否
TaggedPointer,TaggedPointer直接返回。 - 非
nonpointer散列表引用计数+1。 - 对象正在释放不进行操作。
-
nonpointer则extra_rc + 1。-
extra_rc超载的情况下has_sidetable_rc设置为true。 -
extra_rc减半。 - 散列表加一半
extra_rc。
-
1.2 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();
size_t& refcntStorage = table.refcnts[this];
//引用计数+1
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
//1 << 2, +2 是在 refcntStorage 上 +2,因为引用计数存储在 SideTable 的 refcntStorage 位置从1开始,不是从0开始。
//这里+2 相当于+ 0b010,只对1号位置加
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
- 获取对象对应的
SideTable。 - 获取
SideTable中的引用计数表refcnts。 - 从引用计数表中找到
refcntStorage。 -
refcntStorage +2引用计数+1,这里+2是因为加到对应的位上,从1开始。
1.3 sidetable_addExtraRC_nolock
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this];
//从SideTable对应的引用计数表中取出对象的引用计数refcntStorage
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;
//散列表引用计数 + extra_rc的一半。从1号位置开始存,所以需要 delta_rc << 2
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;
}
}
- 获取对象对应的
SideTable。 - 获取
SideTable中的引用计数表refcnts。 - 从引用计数表中找到
refcntStorage。 -
refcntStorage + delta_rc << SIDE_TABLE_RC_SHIF引用计数+ extra_rc最大值的一半。
散列表和extra_rc各存储一半是因为extra_rc可以通过isa直接拿到而散列表需要去查找表然后找到对象的引用计数区域。散列表还有加解锁。在extra_rc中操作方便快速。extra_rc每次存储挪一半是为了避免在retain和release频繁操作的时候而导致散列表频繁操作。
1.4 retain 流程

-
TaggedpPointer直接返回。 - 非
nonpointer isa引用计数表引用计数+1。 -
nonpointer isaextra_rc +1。- 如果
extra_rc上溢出(iOS真机255),extra_rc值减半(128)。 -
extra_rc减少一半的值存入引用计数表。
- 如果
二、 release 源码解析
2.1 release 核心源码
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
//判断是否TaggedPointer,TaggedPointer直接返回
if (slowpath(isTaggedPointer())) return false;
bool sideTableLocked = false;
isa_t newisa, oldisa;
......
retry:
do {
newisa = oldisa;
//非 nonpointer
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
//散列表 引用计数-1
return sidetable_release(sideTableLocked, performDealloc);
}
//在释放 返回
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
return false;
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
//extra_rc - 1。减一后如果extra_rc=0了,则标记carry。
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {//跳转underflow
// don't ClearExclusive()
// printf("newisa.extra_rc: %d\n",newisa.extra_rc);
//存储满的情况下
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
if (slowpath(newisa.isDeallocating()))
goto deallocate;
if (variant == RRVariant::Full) {
if (slowpath(sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!sideTableLocked);
}
return false;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
//是否有散列表
if (slowpath(newisa.has_sidetable_rc)) {
if (variant != RRVariant::Full) {
//清空extra_rc
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
oldisa = LoadExclusive(&isa.bits);
goto retry;
}
// Try to remove some retain counts from the side table.
//将sidetable中取一半(extra_rc 最大值的一半)存到 extra_rc 中。borrow为借过来的值。
auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
//散列表中没有值则标记清空散列表
bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there
if (borrow.borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
bool didTransitionToDeallocating = false;
//extra_rc 值为 borrowed - 1。extra_rc 发生溢出了所以-1存储
newisa.extra_rc = borrow.borrowed - 1; // redo the original decrement too
// printf("newisa.extra_rc111 : %d\n",newisa.extra_rc);
newisa.has_sidetable_rc = !emptySideTable;
//存储
bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
//存储失败则重新存
if (!stored && oldisa.nonpointer) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
uintptr_t overflow;
//借过来的存入到bits。
newisa.bits =
addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
// printf("newisa.extra_rc222 : %d\n",newisa.extra_rc);
newisa.has_sidetable_rc = !emptySideTable;
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
if (stored) {
didTransitionToDeallocating = newisa.isDeallocating();
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
ClearExclusive(&isa.bits);
//没有存储成功则 sidetable 加回去
sidetable_addExtraRC_nolock(borrow.borrowed);
oldisa = LoadExclusive(&isa.bits);
goto retry;
}
// Decrement successful after borrowing from side table.
//清空sidetable
if (emptySideTable)
sidetable_clearExtraRC_nolock();
if (!didTransitionToDeallocating) {
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
}
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
//没有散列表直接释放
deallocate:
// Really deallocate.
ASSERT(newisa.isDeallocating());
ASSERT(isa.isDeallocating());
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
//调用dealloc
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
- 判断是否
TaggedPointer,TaggedPointer直接返回。 - 非
nonpointer isa则直接散列表引用计数-1。 - 如果对象在释放直接返回
false。 -
nonpointer isa则extra_rc - 1。 -
extra_rc溢出的情况判断has_sidetable_rc。 -
has_sidetable_rc为true的情况则sidetable减去extra_rc最大值的一半,值存储到borrow。 -
extra_rc设置为borrow.borrowed - 1(溢出了要减去1再存储,相当于这次的release)。 -
borrow.remaining == 0的情况则设置emptySideTable清空对象对应的SideTable。 -
extra_rc为0的情况则调用发送dealloc消息。
2.2 sidetable_release
uintptr_t
objc_object::sidetable_release(bool locked, bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
//获取对象的 SideTable
SideTable& table = SideTables()[this];
bool do_dealloc = false;
if (!locked) table.lock();
//获取table中对象对象的引用计数 refcnts
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
auto &refcnt = it.first->second;
if (it.second) {
do_dealloc = true;
} else if (refcnt < SIDE_TABLE_DEALLOCATING) {//在释放
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
refcnt |= SIDE_TABLE_DEALLOCATING;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
//引用计数-1
refcnt -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return do_dealloc;
}
- 通过对象获取
SideTable的refcnts。 -
refcnts - 2相当于引用计数-1。 - 如果引用计数为
0则发送dealloc消息。
2.3 sidetable_subExtraRC_nolock
objc_object::SidetableBorrow
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, 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);
//sidetable 减少 extra_rc 的最大值的一半(这里有位运算平移)
size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
ASSERT(oldRefcnt > newRefcnt); // shouldn't underflow
it->second = newRefcnt;
return { delta_rc, newRefcnt >> SIDE_TABLE_RC_SHIFT };
}
- 通过对象获取
SideTable的refcnts。 -
newRefcnt为oldRefcnt减去extra_rc一半。 - 返回
delta_rc以及剩余的newRefcnt。
2.4 release流程

-
TaggedpPointer直接返回false。 - 非
nonpointer isa引用计数表引用计数-1。如果引用计数为0则调用dealloc返回true,否则返回false。 -
nonpointer isaextra_rc -1。- 如果
extra_rc下溢出 ,判断has_sidetable_rc。- 没有引用计数表则调用
dealloc,返回true。 - 有引用计数表则减去
extra_rc最大值的一半(128)存入extra_rc散列表中如果没有值了则清空散列表,返回false。
- 没有引用计数表则调用
- 如果
extra_rc != 0,返回false。
- 如果
总结:
retain 针对相应引用计数位+1,开启nonpointer的情况下,如果引用计数出现上溢出,那么开始分开存储,一半存到散列表。
release 针对相应引用计数位-1,开始nonpointer的情况下,如果引用计数出现下溢出,去散列表借来的引用计数 -1 存到extra_rc,依然下溢出则调用dealloc。
三、散列表(SideTable)
在retain和release的源码中SideTable是通过SideTables获取的:
SideTable& table = SideTables()[this];
那么证明SideTable是有多张的,SideTables的定义如下:
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
......
}
iPhone真机下SideTables中有8张SideTable,其它则为64张。
SideTable对应的结构:
struct SideTable {
spinlock_t slock;//os_unfair_lock
RefcountMap refcnts;//引用计数表
weak_table_t weak_table;//弱引用表
......
}
散列表中存储了锁、引用计数表、弱引用表。
那么为什么使用多张表呢?
由于SideTable有加锁和解锁,如果在整个系统中如果共用一张表那么就会有性能消耗(互斥)。多张表内存可以置空回收。不是每个对象开辟一张表为了效率和性能。
SideTables结构图下:

3.1 引用计数(retainCount)
inline uintptr_t
objc_object::rootRetainCount()
{
//TaggedPointer 对象指针强转返回。
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
//nonpointer
if (bits.nonpointer) {
//extra_rc,之前的版本为 extra_rc + 1。由于之前的版本 alloc 的时候 extra_rc 不进行 +1。目前版本 alloc 的时候进行了赋值 1。
uintptr_t rc = bits.extra_rc;
if (bits.has_sidetable_rc) {
// extra_rc + 散列表引用计数
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
//非 nonpointer 获取散列表中引用计数。
return sidetable_retainCount();
}
-
TaggedPointer直接返回对象的地址强转为unsigned long。 -
nonpointer返回extra_rc + 引用计数表中引用计数。extra_rc这里直接返回没有+1,之前的版本会有+1操作。alloc之前不会对extra_rc赋值为1,现在版本会赋值为1。 - 非
nonpointer直接返回引用计数表中引用计数。
alloc的过程中在进行initIsa的时候对extra_rc进行了赋值:
image.png
SideTable数据内容如下:
(lldb) p table
(SideTable) $5 = {
slock = {
mLock = (_os_unfair_lock_opaque = 775)
}
refcnts = {
Buckets = 0x0000000102b04080
NumEntries = 1
NumTombstones = 0
NumBuckets = 4
}
weak_table = {
weak_entries = 0x0000000000000000
num_entries = 0
mask = 0
max_hash_displacement = 0
}
}
refcnts中存储了引用计数的Buckets,其中存储了DisguisedPtr<objc_object>(包装了引用计数),与关联对象的存储有些类似。
3.2 弱引用
有如下代码:
NSObject *obj = [NSObject alloc];
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj);//1
__weak typeof(id) weakObjc = obj;
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc);//2
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj);//1
运行输出:
1 - <NSObject: 0x101622330>
2 - <NSObject: 0x101622330>
1 - <NSObject: 0x101622330>
按照正常理解weak不增加引用计数,obj输出1没问题。weakObjc的引用计数为什么输出2?
3.2.1 弱引用表
要了解weak的引用计数首先要清楚weak表的存储逻辑。通过汇编跟踪发现__weak修饰的变量会进入objc_initWeak:

那么__weak是怎么与objc_initWeak关联起来的呢?
在llvm中有相关的映射,weak和__weak最终映射到了objc_initWeak:

弱引用的存储与释放:
id
objc_initWeak(id *location, id newObj)
{
//对象不存在直接返回。
if (!newObj) {
//weak 指针置为 nil
*location = nil;
return nil;
}
//执行存储操作
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
void
objc_destroyWeak(id *location)
{
//与init同一个函数。传递 newObj 为 nil
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
最终都会调用同一个函数storeWeak。
3.2.1.1 storeWeak
//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;
// 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:
//整体是SideTables
if (haveOld) {//是否有旧值,第一次进来没有。
oldObj = *location;
//取旧表
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
//取weak指针对应的新表地址
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
//锁定两张表
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
......
// 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 ? 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;
}
- 获取
oldTable与newTable,newTable用来存储,oldTable用来释放。 - 通过
SideTables获取obj对应的SideTable。 - 如果是释放调用
weak_unregister_no_lock释放弱引用指针。参数传递SideTable的weak_table以及obj和弱引用指针。 - 如果是存储调用
weak_register_no_lock存储弱引用指针。参数传递SideTable的weak_table以及obj和弱引用指针。
3.2.1.2 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;
//找到对象的 weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//将弱引用指针从weak_entry_t中移除。
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) {
//如果entry为空了,则将entry从整个weak_table中移除
weak_entry_remove(weak_table, entry);
}
}
}
- 在
weak_table中找到对象对应的weak_entry_t。 - 调用
remove_referrer遍历weak_entry_t的inline_referrers或者referrers将对应index位置的值置为nil,并且num_refs - 1。 - 如果
weak_entry_t中已经没有值了,则调用weak_entry_remove将entry从weak_table中清除并且释放空间。
3.2.1.3 weak_register_no_lock
//对象对应的全局弱引用表,对象指针,弱引用指针
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;
......
// now remember it and where it is being stored
weak_entry_t *entry;
//根据弱引用对象从weak_table中找出weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//将弱引用指针加入entry
append_referrer(entry, referrer);
}
else {
//通过弱引用指针与对象创建new_entry
weak_entry_t new_entry(referent, referrer);
//weak_table扩容
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;
}
- 在
weak_table中找到对象对应的weak_entry_t。 - 找到对应的
weak_entry_t调用append_referrer将弱引用指针加入weak_entry_t(因为释放过程中有置空操作,所以找空位nil加入,这个过程中可能会进行扩容)。 - 找不到则根据对象和弱引用指针创建
weak_entry_t。- 调用
weak_grow_maybe尝试扩容。 - 调用
weak_entry_insert将创建的weak_entry_t加入weak_table。
- 调用
散列表完整结构:

3.2.2 weak 的引用计数
到目前为止仍然解释不了为什么之前的案例弱引用计数为2,修改代码如下:
NSObject *obj = [NSObject alloc];
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//1
__weak typeof(id) weakObjc = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//1
NSObject *obj2 = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj2)),obj2,&obj2);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);//3
__weak typeof(id) weakObjc2 = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);//3
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc2)),weakObjc2,&weakObjc2);//3
输出:
1 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f8
1 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f0
3 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f8
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
3 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f8
3 - <NSObject: 0x101a1a730> - 0x7ffeefbff4e8
obj引用计数为2很好理解,weakObjc多次指向也只增加了一次。weakObjc的引用计数看着是对象的引用计数+weak的1次。
CFGetRetainCount调用的是retainCount,那么显然获取的是obj的引用计数,那么多的1肯定做了额外的处理。
有如下代码,对NSLog打断点:
NSObject *obj = [NSObject alloc];
__weak typeof(id) weakObjc = obj;
NSLog(@"%zd",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)));
在调用retainCount之前调用了objc_loadWeakRetained:

3.2.2.1 objc_loadWeakRetained
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// fixme std::atomic this load
//通过 weak 指针获取 obj 临时变量。此时引用计数仍然不变。
obj = *location;
if (obj->isTaggedPointerOrNil()) return obj;
table = &SideTables()[obj];
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
//可以尝试 table->unlock() 然后读取 _objc_rootRetainCount(obj)
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());
//引用计数 +1,此时 obj 的引用计数变了。
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls);
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
}
else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
result = nil;
}
}
else {
table->unlock();
class_initialize(cls, obj);
goto retry;
}
}
//这个时候返回的`retainCount`就多了1。
table->unlock();
return result;
}
- 获取弱引用指针对应的
obj。 - 调用
rootTryRetain对obj引用计数+1。
验证:

在调用了objc_loadWeakRetained后调用了retainCount获取obj的引用计数。然后调用objc_release释放这次增加的引用计数。
weak并不会增加引用计数,CFGetRetainCount如果获取的是weak指针的引用计数会先调用objc_loadWeakRetained对对象的引用计数+1,再调用retainCount获取引用计数,然后调用objc_release对对象的引用计数-1。
那么为什么weak的引用计数要临时+1呢?
为了在CFGetRetainCount的过程中,weakObjc不被释放。
__weak typeof(id) weakObjc = nil;
{
NSObject *obj = [NSObject alloc];
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);
weakObjc = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);
}
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);
上面的代码虽然会崩溃,仍然可以断点查看weakObjc信息:

弱引用指针指向的对象已经释放了,弱引用指针还没有释放,出了弱引用指针作用域才释放。这么做的好处是弱引用指针的管理与对象的管理完全分开了。
弱引用表调用流程:
- 弱引用指针存储在全局
SideTables中。 - 通过对象获取
SideTable取到其中的weak_table。 - 创建
weak_entry_t,将弱引用指针包装后加入创建的weak_entry_t的referrers(inline_referrers)中。 - 判断是否需要扩容
weak_table。 - 将创建的
weak_entry_t加入weak_table中。

四、strong & unsafe_unretain
NSObject *obj = [NSObject alloc];
NSObject *obj1 = obj;
HPObject *objc = [HPObject alloc];
objc.objc = obj;
strong修饰的属性或者变量,当属性变量赋值的时候会调用objc_storeStrong(编译时期确定):



objc_storeStrong源码如下:
void
objc_storeStrong(id *location, id obj)
{
//旧值
id prev = *location;
if (obj == prev) {
return;
}
//retain 新值
objc_retain(obj);
//赋值新值给指针
*location = obj;
//release 旧值
objc_release(prev);
}
在objc_storeStrong过程中会先retain新值然后赋值,最后release旧值。
在源码中会根据变量的修饰符来确定调用的方法:

如下代码:
@property (nonatomic, strong) NSObject *objc;
@property (nonatomic, weak) NSObject *objc1;
@property (nonatomic, unsafe_unretained) NSObject *objc3;
编译后对应的汇编伪代码:
-(void)setObjc:(void *)arg2 {
objc_storeStrong(self + 0x40, arg2);
return;
}
-(void)setObjc1:(void *)arg2 {
objc_storeWeak(self + 0x48, arg2);
return;
}
-(void)setObjc3:(void *)arg2 {
self->_objc3 = arg2;
return;
}
-
strong修饰的变量底层会调用objc_storeStrong先进行新值的retain然后赋值,最后旧值进行release。 -
weak底层调用objc_storeWeak将weak指针加入弱引用表中。在dealloc的时候会自动将weak指针置为nil。 -
unsafe_unretained直接用新值赋值指针,在dealloc的时候并不会自动置为nil,可能会造成野指针访问。
总结:
retain针对相应引用计数位+1,开启nonpointer的情况下,如果引用计数出现上溢出,那么开始分开存储,一半存到散列表。release针对相应引用计数位-1,开启nonpointer的情况下,如果引用计数出现下溢出,去散列表借来的引用计数-1存到extra_rc,依然下溢出则调用dealloc。-
散列表
iPhone真机下SideTables中有8张SideTable,其它则为64张。- 每张
SideTable包含了 引用计数表 和 弱引用表。
- 引用计数表(
RefcountMap)的Buckets存储了对象对应的引用计数的包装。 - 弱引用表(
weak_table_t)的weak_entries存储了对象的弱引用指针weak_entry_t,weak_entry_t中存储了指向的对象和指向该对象的弱引用指针集合referrers。referrer中存储了包装的弱引用指针。 -
引用计数
TaggedPointer直接返回对象的地址强转为unsigned long。nonpointer返回extra_rc + 引用计数表中引用计数。extra_rc这里直接返回没有+1,之前的版本会有+1操作。alloc之前不会对extra_rc赋值为1,现在版本会赋值为1。- 非
nonpointer直接返回引用计数表中引用计数。 weak并不会增加引用计数,CFGetRetainCount会调用objc_loadWeakRetained对weak指向的对象引用计数+1,调用完retainCount后调用objc_release对引用计数-1。
- 弱引用表与对象是分开管理的,各自在作用域处理自身逻辑。
