前言
我们大家都知道,iOS
是使用引用计数对对象内存进行管理,alloc
后引用计数为1
,对对象发送retain
消息时,引用计数会进行+1
,发送release
消息时进行-1
。当引用计数为0
时会自动对对象进行销毁。那么 retain
,release
,dealloc
这些方法在底层做了一些什么呢?带着这样的问题,我们开始今天的探索。
retain
方法解析:
要探索retain
,我们最后使用MRC
项目,能清晰调用流程
选中项目Target,选择Build Settings
搜索automatic
,然后将如下图所示的选项修改为NO
按上面操作后,我们的下面已经是MRC
了。如下声明一个XQPerson
类:
@interface XQPerson : NSObject
@end
@implementation XQPerson
@end
复制代码
在main.m
调用:
程序运行起来后打开汇编调试,如下图所示
我们发现,并没有按预想的调用retain
方法,反而调用了objc_retain
函数,参考alloc
方法被替换为objc_alloc
函数,我们可以推断出这又是编译器将retain
替换为objc_retain
函数
objc_retain
函数解析
id
objc_retain(id obj)
{
if (obj->isTaggedPointerOrNil()) return obj;
return obj->retain();
}
复制代码
- 判断是否
taggedPointer
类型,如果是则直接返回,不是则调用retain
函数
objc_object::retain
函数:
inline id
objc_object::retain()
{
ASSERT(!isTaggedPointer());
return rootRetain(false, RRVariant::FastOrMsgSend);
}
复制代码
- 直接调用了
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
函数,并传参false
和FastOrMsgSend
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
函数(重点)解析:
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
oldisa = LoadExclusive(&isa.bits);
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())) {//判断当前类或父类(除NSObject外)是否重写了retain等方法
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;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain(sideTableLocked);
}
// don't check newisa.fast_rr; we already called any RR overrides
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
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);//表示isa的ExtraRC已装满,再次调用此方法,传入variant = RRVariant::Full
}
// 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;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
// 比较 isa.bits 和 oldisa.bits 如果相等,将 newisa.bits赋值给 isa.bits并退出循环
} while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
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;
}
复制代码
variant == RRVariant::FastOrMsgSend
,此时判断为真,oldisa.getDecodedClass(**false**)->hasCustomRR()
判断当前类或父类(除NSObject
外)是否重写了retain
等方法,如果是则重新调用当前对象的retain
方法。!oldisa.nonpointer
判断是否nonpointer
(通常类对象都是nonpointer
,实例对象除非配置了环境变量才有可能为真,此时判断如果是类对象则直接返回- 进入
do while
循环,首先判断是否nonpointer
,如果!newisa.nonpointer
为真,执行sidetable_retain
,直接使用散列表进行存储 newisa.isDeallocating()
判断是否正在释放,如果是则直接返回newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);
对isa
的extra_rc
进行+1
,RC_ONE
根据架构不同会有差异,以arm64
为例,此时为1ULL<<45
,isa
结构中extra_rc
从第45
位开始,工占用19
位,所以此时对newisa.bits
进行加1ULL<<45
即为对extra_rc
加1
,当超出isa.bits
范围是,carry
会被赋值slowpath(carry)
判断isa
是否存满,variant != RRVariant::Full
,此时variant
为RRVariant::FastOrMsgSend
,进入此判断,调用rootRetain_overflow
函数,而rootRetain_overflow
实际上会再次调用objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
函数,并传入参数false
和Full
- 当调用
rootRetain_overflow
函数再次进入当前函数后,继续执行下面的代码newisa.extra_rc = RC_HALF(1ULL<<18);
将extra_rc
设置为满载的一半,newisa.has_sidetable_rc = true;
设置has_sidetable_rc
为1
,isa
标记使用了sidetable
存储引用计数 slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits))
比较 isa.bits 和 oldisa.bits 如果相等,将 newisa.bits赋值给 isa.bits并退出循环sidetable_addExtraRC_nolock(RC_HALF);
将另一半引用计数存入散列表
rootRetain_overflow
函数解析:
NEVER_INLINE id
objc_object::rootRetain_overflow(bool tryRetain)
{
return rootRetain(tryRetain, RRVariant::Full);
}
复制代码
- 实际重新调用了
rootRetain
函数并传值variant = RRVariant::Full
sidetable_addExtraRC_nolock
函数解析:
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
ASSERT(isa.nonpointer);
// 取出散列表
SideTable& table = SideTables()[this];
// 取出存储在散列表的引用计数
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;
// 散列表引用计数 + RC_HALF(1 << 18) << 2,由于从第三位开始才存储引用计数,所以实际引用计数增加了 1 << 18个
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
//判断是否超出
if (carry) {
//如果超出则将 修改refcntStorage,再次进入此函数时oldRefcnt & SIDE_TABLE_RC_PINNED为true则会直接返回
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else {
// refcntStorage 赋新值
refcntStorage = newRefcnt;
return false;
}
}
复制代码
SideTable& table = SideTables()[this];
取出散列表size_t& refcntStorage = table.refcnts[this];
取出引用计数,size_t&表示直接对指向区域进行赋值oldRefcnt & SIDE_TABLE_RC_PINNED
判断散列表是否已满,如满直接返回addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry)
对oldRefcnt
增加RC_HALF(1 << 18) << 2
,由于是散列表引用计数从第3
位开始存储,所以实际实际引用计数增加了1 << 18
个,即为isa.extra_rc
容量的一半。if (carry)
判断是否已装满,如已装满,增加装满的标识并返回
retain
方法小结
当对一个对象发送retain
消息时,会优先将引用计数存储在isa.extra_rc
,当超出能存储的范围时,将需要借助散列表进行存储,此时会将一半的引用计数存入散列表
release
方法解析
对release
方法的探索和retain
类似,只需要将调用 retain
改为 release
即可
继续查看汇编,显示如下:
由汇编分析可知,release
和 retain
一样,也被替换成了objc_release
objc_release
函数解析:
void
objc_release(id obj)
{
if (obj->isTaggedPointerOrNil()) return;
return obj->release();
}
复制代码
- 判断是否taggedPointer,如果是直接返回
- 调用
release
函数
objc_object::release
函数解析:
inline void
objc_object::release()
{
ASSERT(!isTaggedPointer());
rootRelease(true, RRVariant::FastOrMsgSend);
}
复制代码
- 直接调用
rootRelease
函数并传参performDealloc
=true
,variant
=RRVariant::FastOrMsgSend
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return false;
bool sideTableLocked = false;
isa_t newisa, oldisa;
oldisa = LoadExclusive(&isa.bits);
// 判断是否 FastOrMsgSend
if (variant == RRVariant::FastOrMsgSend) {
// These checks are only meaningful for objc_release()
// They are here so that we avoid a re-load of the isa.
//判断是否重写release等方法
if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
ClearExclusive(&isa.bits);
if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
swiftRelease.load(memory_order_relaxed)((id)this);
return true;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
return true;
}
}
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 false;
}
}
retry:
do {
newisa = oldisa;
//判断是否nonpointer,如果不是,则表示只使用了散列表存储引用计数,直接处理散列表的引用计数
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
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;
//对isa.extra_rc进行--
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {// 不能再减了跳转 underflow
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
//引用计数进行处理后,判断是否释放,如果引用计数为0 则跳转 deallocate
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) {
ClearExclusive(&isa.bits);
// 再次调用 rootRelease函数并传入 variant = RRVariant::Full
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.
// 从散列表中取出RC_HALF(1 << 18)个引用计数
auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
// borrow.remaining表示散列表中是否还有引用计数
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;
//对 newisa.extra_rc进行赋值,将 borrow.borrowed - 1个引用计数存入newisa.extra_rc
newisa.extra_rc = borrow.borrowed - 1; // redo the original decrement too
// 标记是否还使用散列表存储引用计数
newisa.has_sidetable_rc = !emptySideTable;
// 判断 isa.bits 和 oldisa.bits是否相等,如果相等,将 newisa.bits赋值给 isa.bits 并返回true
bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
// 判断是否保存成功,如果未成功,则对oldisa进行修改
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;
//对 oldisa.bits进行 的引用计数++后再次赋值给 newisa.bits
newisa.bits =
addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
newisa.has_sidetable_rc = !emptySideTable;
// 判断是否存满,未存满再次更新isa
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_addExtraRC_nolock(borrow.borrowed);
oldisa = LoadExclusive(&isa.bits);
goto retry;
}
// Decrement successful after borrowing from side table.
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);
if (performDealloc) {
//发送dealloc消息
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
复制代码
rootRelease
函数和rootRetain
函数有很多相似之处,相同的判断逻辑这里就不赘述了,重点说明一下underflow
和deallocate
流程slowpath(newisa.has_sidetable_rc)
判断散列表中是否有引用计数,如果有,调用rootRelease_underflow
函数,再次调用rootRelease
并传入variant = RRVariant::Full
auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
从散列表中取出引用计数,bool emptySideTable = borrow.remaining == 0
散列表中是否还有引用计数newisa.extra_rc = borrow.borrowed - 1
将extra_rc
赋值为borrow.borrowed - 1
(由于此时是release
所以-1
)newisa.has_sidetable_rc = !emptySideTable;
标记是否使用散列表存储引用计数bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
,判断 isa.bits 和 oldisa.bits是否相等,如果相等,将 newisa.bits赋值给 isa.bits 并返回trueif (!stored && oldisa.nonpointer)
判断是否存储成功,如果未成功,且为nonpointer
,再次使用oldisa.bits
进行存储,if (!stored)
判断是否存储成,如果未成功,则将引用计数再次存入散列表deallocate
如果引用计数为0
,则直接发送dealloc
消息,将对象进行释放
release
方法小结
当对一个对象发送release
消息时,首先会将isa.extra_rc -1
,当isa.extra_rc
为0
时,判断散列表中是否有引用计数,如果有,从散列表中取出extra_rc
容量一半的引用计数存入extra_rc
并-1
,当extra_rc
和散列表中都没有引用计数了就对对象发送一个dealloc
消息,将对象释放
dealloc
方法分析:
通过对release
方法的探索,我们已经知道了当对象引用计数为0
时会直接对对象发送dealloc
消息,我们打开dealloc
方法的源码如下:
- (void)dealloc {
_objc_rootDealloc(self);
}
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
复制代码
- 封装层级比较深,顺序依次为
dealloc
->_objc_rootDealloc
->rootDealloc
,rootDealloc
判断是否为nonpointer
,同时没有被弱引用持有,没有关联对象,没有Cxx析构函数,没有使用散列表存储引用计数,则直接free
object_dispose
函数解析:
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
复制代码
obj
不为空直接调用objc_destructInstance
objc_destructInstance
函数解析:
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();//是否有cxx析构函数
bool assoc = obj->hasAssociatedObjects();//是否有关联对象
// This order is important.
if (cxx) object_cxxDestruct(obj);// 清除成员变量(strong等修饰)
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
复制代码
- 判断是否有cxx析构函数,如果有则调用
object_cxxDestruct
,清除成员变量 - 判断是否有关联对象,如果有调用
_object_remove_assocations
,清除关联对象 clearDeallocating
清除和对象相关的弱引用表
object_cxxDestruct
函数解析:
分析此函数,要先分析,在什么时候bool cxx = obj->hasCxxDtor()
为true
,首先还是之前定义的XQPerson
,添加一个int age
属性如下所示
@interface XQPerson : NSObject
@property(nonatomic,assign)int age;
@end
复制代码
运行起来后,发现rootDealloc
函数已经直接释放了,所以可以确定,此时没有cxx
析构函数
对XQPerson
类进行修改,增加一个strong
修饰的name
属性如下:
@interface XQPerson : NSObject
@property(nonatomic,copy)NSString* name;
@end
复制代码
运行起来后发现, bool cxx = obj->hasCxxDtor()
为true,由此可以确定,有strong
修饰的属性的类的实例对象有 cxx
析构函数,按照此方法,分别验证了 retain
,copy
,weak
等修饰的属性都存在cxx
析构函数,而只有assign
和unsafe_unretained
修饰属性的对象则没有
object_cxxDestruct
函数解析:
void object_cxxDestruct(id obj)
{
if (obj->isTaggedPointerOrNil()) return;
object_cxxDestructFromClass(obj, obj->ISA());
}
复制代码
- 直接调用
object_cxxDestructFromClass
函数
static void object_cxxDestructFromClass(id obj, Class cls)
{
void (*dtor)(id);
// Call cls's dtor first, then superclasses's dtors.
for ( ; cls; cls = cls->getSuperclass()) {
if (!cls->hasCxxDtor()) return;
// 查找缓存和方法列表
dtor = (void(*)(id))
lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);
if (dtor != (void(*)(id))_objc_msgForward_impcache) {
if (PrintCxxCtors) {
_objc_inform("CXX: calling C++ destructors for class %s",
cls->nameForLogging());
}
//获取到imp后执行
(*dtor)(obj);
}
}
}
复制代码
- 通过调试
SEL_cxx_destruct
=.cxx_destruct
,通过添加符号断点-[XQPerson .cxx_destruct]
,由实测发现strong
,retain
,copy
修饰的属性,如下所示:
从上图可以得知,会执行objc_storeStrong
函数
objc_storeStrong
函数分析:
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
复制代码
- 此时传入
obj
为nil
,由此可见,实际是将属性指向nil
,并执行一次release
weak修饰的属性则有所不同,如下图所示:
从上图可知,weak
修饰的属性会调用objc_destroyWeak
函数,对弱引用进行置空
dealloc
方法小结
dealloc
方法主要做的事情是调用object_cxxDestruct
函数清空对象的成员变量,_object_remove_assocations
函数清空对象的关联对象,clearDeallocating
函数清空对象的散列表- 需要注意的,在
ARC
重写dealloc
会自动调用父类的dealloc
方法,但是在MRC
并不会自动调用父类的dealloc
方法,所以MRC
开发时,若重写了dealloc
方法,必须要调用父类的dealloc
方法。