Objective-C
内存管理的核心思想就是通过对象的引用计数来对内存对象的生命周期进行控制。说直白一点,就是调用retain
会加1,调用release
就会减1,引用计数清零或者调用dealloc
就销毁。
引用计数
引用计数,即为对象被持有的次数。是内存管理的核心点。下面我们来看一个关于引用计数的例子:
- (void)testRefCount {
NSObject *obj = [NSObject alloc];
NSLog(@"==refCount==%ld==", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
}
运行程序,结果为1。可是alloc
的流程中并没有对引用计数操作的流程,那么这个打印为什么是1呢?来看看retainCount
的源码:
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
可以看到,引用计数的总值就是isa
里面extra_rc
的取值和散列表中引用计数表的取值外加1,我们新alloc
的对象,引用计数打印为1就是因为这个加的这个1,其实新alloc
出来的对象引用计数为0。
那么引用计数是存储在哪里的,retain
和release
到底是如何处理的呢?下面我们先来看一下retain
:
retain
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
inline id
objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
可以看到,调用objc_retain
首先会判断是否是isTaggedPointer
,如果是就直接返回。接着会判断对象没有自定义retain/release
方法就会调用rootRetain
,否则就通过objc_msgSend
发送SEL_retain
消息。
id
objc_object::rootRetain()
{
return rootRetain(false, false);
}
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;
if (slowpath(!newisa.nonpointer)) {
// 不是nonpointer isa 散列表处理
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// 如果正在deallocating 不做处理
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
// 是nonpointer isa extra_rc++
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// 超出之后一半存到散列表中,一半放在extra_rc 并且处理isa的extra_rc标志位和has_sidetable_rc
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
进入到rootRetain
会进行如下操作:
- 判断是否是
isTaggedPointer
,TaggedPointer
是不需要维护引用计数的,直接返回。
- 判断是否是
- 不是
TaggedPointer
,就获取对象的isa
,判断是不是nonpointer isa
- 不是
- 不是
nonpointer isa
,交给散列表处理,对引用计数进行++
操作,然后返回
- 不是
- 判断是不是正在
deallocating
,是的话直接返回
- 判断是不是正在
- 是
nonpointer isa
,对isa
的标志位extra_rc
执行++
操作
- 是
- 如果计数超出
extra_rc
能存储的范围了,就将其中的一半存在extra_rc
,并把has_sidetable_rc
标志位置为1。然后拷贝另外一半放入散列表进行保存。
- 如果计数超出
散列表是多张表,由于性能和安全的考虑,是多张而不是一张,但是多张并不是每个对象就一张。散列表的存储引用计数的方式如下,也就是对存储的引用计数进行++
操作:
bool
objc_object::sidetable_tryRetain()
{
SideTable& table = SideTables()[this];
bool result = true;
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) {
table.refcnts[this] = SIDE_TABLE_RC_ONE;
} else if (it->second & SIDE_TABLE_DEALLOCATING) {
result = false;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
it->second += SIDE_TABLE_RC_ONE;
}
return result;
}
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
release
retain
是引用计数+1
,而release
是引用计数-1
,流程是相辅相成的。调用release
还是会进入到objc_release
方法。
void
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
inline void
objc_object::release()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
rootRelease();
return;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}
可以看到,调用objc_release
也会先判断是否是isTaggedPointer
,如果是就直接返回。接着会判断对象没有自定义retain/release
方法就会调用rootRelease
,否则就通过objc_msgSend
发送SEL_release
消息。
bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
// isTaggedPointer 不做处理 直接返回
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
// 不是nonpointer_isa 对散列表中的引用计数进行处理
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
// nonpointer_isa 对isa的extra_rc 进行--操作
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
if (slowpath(carry)) {
// 如果不够减,则需要去散列表的引用计数表中借位
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
newisa = oldisa;
// 此时isa的extra_rc已经清零 没有计数了
// isa的散列表引用标志位有值
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
// 递归调用
return rootRelease_underflow(performDealloc);
}
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// 散列表没加锁,加锁 递归
goto retry;
}
// 把散列表里存储的引用计数取出来
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
// 散列表中的引用计数如果大于0
if (borrowed > 0) {
// 对散列表的引用计数做--操作 然后存入isa的extra_rc
newisa.extra_rc = borrowed - 1;
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
// 如果没有成功的存入 isa的extra_rc 那就再存一遍
......
}
if (!stored) {
// 二次存入还是没有成功 就把数据放回到散列表的引用计数表
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// 从散列表借位--成功
sidetable_unlock();
return false;
}
else {
// 散列表的引用计数也是空的
}
}
// isa 没有在deallocating中 那抛出异常
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
}
// 将isa置为deallocating,然后再来递归一遍
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__sync_synchronize();
if (performDealloc) {
// 发送一个SEL_dealloc的消息
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
}
梳理一下,进入到rootRelease
之后的流程如下:
- 判断是否是
isTaggedPointer
,TaggedPointer
是不需要维护引用计数的,直接返回。
- 判断是否是
- 不是
TaggedPointer
,就获取对象的isa
,判断是不是nonpointer isa
- 不是
- 不是
nonpointer isa
,交给散列表处理,对引用计数进行--
操作,如果散列表的引用计数清零,就需要对该对象发送SEL_dealloc
信息,执行dealloc
操作,然后返回。
- 不是
- 如果是
nonpointer_isa
就对isa
的extra_rc
进行--
操作,当extra_rc
计数为0,不够减的时候,就需要从散列表的引用计数表借位减。
- 如果是
- 判断
isa
的has_sidetable_rc
是否有值,有值就进行第6步,没有值就进行第8步
- 判断
- 获取散列表的引用计数,如果计数等于0,就对该对象发送
SEL_dealloc
信息,执行dealloc
操作。
- 获取散列表的引用计数,如果计数等于0,就对该对象发送
- 如果从散列表获取的引用计数大于0,将计数减1,然后存入
isa
的extra_rc
,返回。如果没有存入成功,则进行2次递归存储,如果还是没有成功,就将计数存入散列表,继续进行一次递归操作。
- 如果从散列表获取的引用计数大于0,将计数减1,然后存入
- 判断
isa
是否正处于deallocating
,如果没有那就抛出异常。
- 判断
- 对该对象发送
SEL_dealloc
信息,执行dealloc
操作。
- 对该对象发送
散列表的引用计数表release
引用计数的操作:
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) {
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
it->second -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
dealloc
当页面销毁或者对象销毁的时候就会进入dealloc
方法进行相关的处理。
- (void)dealloc {
_objc_rootDealloc(self);
}
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
// TaggedPointer 不用处理引用计数
if (isTaggedPointer()) return; // fixme necessary?
// 是nonpointer
// 没有弱引用表、没有关联对象、没有c++析构器、没有散列表引用计数
// 直接释放
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// 存在c++析构函数 调用析构
if (cxx) object_cxxDestruct(obj);
// 存在关联对象 删除关联对象
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// 非non-pointer isa
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// non-pointer isa
clearDeallocating_slow();
}
assert(!sidetable_present());
}
void
objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
// 清除弱引用表
weak_clear_no_lock(&table.weak_table, (id)this);
}
// 清除散列表中的引用计数表的相关信息
table.refcnts.erase(it);
}
table.unlock();
}
void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
// 清除弱引用表
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
// 清除散列表中的引用计数表的相关信息
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
总结一下,调用dealloc
方法的流程:
- 如果是
TaggedPointer
,则不用处理引用计数,返回
- 如果是
- 如果是
nonpointer_isa
,且没有弱引用表、没有关联对象、没有c++析构器、没有散列表引用计数,那就直接释放,否则进入第3步,调用object_dispose()
- 如果是
- 调用
objc_destructInstance(obj)
,然后在调用free(obj)
- 调用
-
objc_destructInstance()
方法中,判断如果存在cxx
析构器则需要调用析构方法,如果存在关联对象需要删除关联对象。
-
- 清除弱引用表中的相关信息,清除散列表中引用计数表的信息。
弱引用表的释放详见weak
原理,此处就不赘述。
网友评论