OC内存管理-ARC&MRC、散列表
-
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 流程
retain流程-
TaggedpPointer
直接返回。 - 非
nonpointer isa
引用计数表引用计数+1
。 -
nonpointer isa
extra_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流程
release流程-
TaggedpPointer
直接返回false
。 - 非
nonpointer isa
引用计数表引用计数-1
。如果引用计数为0
则调用dealloc
返回true
,否则返回false
。 -
nonpointer isa
extra_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
直接返回引用计数表中引用计数。
image.png
alloc
的过程中在进行initIsa
的时候对extra_rc
进行了赋值:
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
。
验证:
image.png
在调用了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
。
- 弱引用表与对象是分开管理的,各自在作用域处理自身逻辑。