retain release在曾经的MRC时代经常活跃在我们眼前,现在的ARC时代我们很少见到他们了,但是不是说他们完全消失了,而是在编译阶段,编译器自动给我们插入了,下面我们就去看下他们的实现
首先我们要知道对象的引用计数都是报错在isa的extra_rc与一个全局的SideTable的hash表中,然后我们先从retain方法去分析
找到retain方法的实现
- (id)retain {
return ((id)self)->rootRetain();
}
id objc_object::rootRetain()
{
return rootRetain(false, false);
}
这两个方法只是一个包装,然后看到有调用id objc_object::rootRetain(bool tryRetain, bool handleOverflow)方法,这个方法是整个retain的核心
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;
oldisa = LoadExclusive(&isa.bits);//加载isa
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// do not check newisa.fast_rr; we already called any RR overrides
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ 将isa的值+1(isa中extra_rc+1)
if (slowpath(carry)) {//extra_rc 不足以保存引用计数,
// newisa.extra_rc++ overflowed
if (!handleOverflow) {//并且 handleOverflow = false。走该循环条件
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);//重新执行rootRetain(bool tryRetain, bool handleOverflow)方法并将handleOverflow设置为true
}
// 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;//因为 extra_rc 已经溢出了,所以要更新它的值为 RC_HALF:二进制 10000000
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));//StoreExclusive(&isa.bits, oldisa.bits, newisa.bits) 更新 isa 的值
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();
return (id)this;
}
这个方法会分为两种情况,一种是在引用计数没有超出extra_rc的位数,在前面我们分析过extra_rc在arm64架构下为19位,一种是引用计数已经超出了extra_rc位数,如果没有超出那么情况很简单,只是单纯的进行extra_rc+1操作,其核心就是
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);
这句代码就是将extra_rc进行加一,但是紧跟着我们可以看到又有一个if判断,这里面就是处理,如果引用计数超出所做的处理
if (slowpath(carry)) {//extra_rc 不足以保存引用计数,
// newisa.extra_rc++ overflowed
if (!handleOverflow) {//1, handleOverflow = false。走该循环条件
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);//重新执行rootRetain(bool tryRetain, bool handleOverflow)方法并将handleOverflow设置为true
}
// 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;
}
首先判断了引用计数是否超出,然后在进行一次判断如果handleOverflow == false的时候直接return rootRetain_overflow(tryRetain);,我们上面可以看到,在调用rootRetain方法的时候, handleOverflow传入的就是false所以这个方法一定会走
id
objc_object::rootRetain_overflow(bool tryRetain)
{
return rootRetain(tryRetain, true);
}
rootRetain_overflow的方法很简单,就是将handleOverflow设置为true然后重新调用rootRetain方法,之后将extra_rc值设置为RC_HALF这个宏,并将has_sidetable_rc与transcribeToSideTable设置为true
# if __arm64__
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define RC_HALF (1ULL<<7)
跳出循环后因为transcribeToSideTable为true将调用bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)方法
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
//将引用计数的一半保留到表中
sidetable_addExtraRC_nolock(RC_HALF);
}
我们看下sidetable_addExtraRC_nolock的实现
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)//yty delta_rc = RC_HALF 1ULL<<18
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];//以对象地址为key获取对应的SideTable
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;
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
/*
delta_rc << SIDE_TABLE_RC_SHIFT
SIDE_TABLE_RC_SHIFT == 2 1ULL<<20 因为 refcnts 中的 64 为的最低两位是有意义的标志位,所以在使用 addc 时要将 delta_rc 左移两位,获得一个新的引用计数 newRefcnt。
64位的倒数第一位标记当前对象是否被weak指针指向(1:有weak指针指向); 64位的倒数第二位标记当前对象是否正在销毁状态(1:处在正在销毁状态) 其他的62位都可以用于存储retainCount.
*/
if (carry) {
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else {//保存多余的retaincount
refcntStorage = newRefcnt;
return false;
}
}
在这个方法中我们可以看到首先以对象本身的地址为key取出对应的SideTable
SideTable是一个全局的hash表,它里面存储了多出的引用计数与
weak指针
然后从SideTable中的RefcountMap refcnts再以地址为key取出当前的引用计数refcntStorage,然后在原始的引用计数的基础上加上传入的引用计数<<2
向右偏移两位的原因是,
RefcountMap refcnts的最后两位有特殊的标示意义
倒数第一位标记当前对象是否被weak指针指向(1:有weak指针指向);
倒数第二位标记当前对象是否正在销毁状态(1:处在正在销毁状态);
所以,64位环境下只有62位是保存溢出的引用计数的
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1)
再往下我们可以看到,SideTable中的引用计数也是有可能会溢出的,这时候,就撤销这次的行为;否则将新的引用计数存储进去
由此我们可以得到,当对象的引用计数没有超出extra_rc时存储在extra_rc,而超出后则溢出的部分存储在SideTable中
上面我们了解了retain是怎么对引用计数进行+操作的,下面我们去看看release对引用计数怎么进行-操作的,首先
- (oneway void)release {
((id)self)->rootRelease();
}
bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
与retain类似rootRelease方法是整个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);//获取isa
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc-- 将 isa 中的引用计数减一
if (slowpath(carry)) {//如果是从SideTable借位了
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));//调用 StoreReleaseExclusive 方法保存新的 isa
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) {//判断是否借助Sidetable存储引用计数
if (!handleUnderflow) {//与retain作用相似 重新调用本方法(递归) rootRelease(bool performDealloc, bool handleUnderflow)并将handleUnderflow=true
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);//试图从side table中删除计数 并返回所删除的引用计数
if (borrowed > 0) {//借出的引用计数大于0
// 尝试将引用计数放入extra_rc中
newisa.extra_rc = borrowed - 1;
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {//放入extra_rc失败
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);
}
}
}
if (!stored) {//再试一次依旧不能将多余的引用计数放入isa中,于是重新将多余的引用计数在放入side table中
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
sidetable_unlock();
return false;
}
else {
//Side table是空的不需要做处理了 去做dealloc操作
}
}
if (slowpath(newisa.deallocating)) {
//当前对象正在释放
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
}
newisa.deallocating = true;//将deallocating设置为true 标志正在释放中
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__sync_synchronize();
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);//如果可以释放 直接调用objc_msgSend调用dealloc方法
}
return true;
}
相对于retain操作,release就相对会复杂一些,首先判断是否是isTaggedPointer,如果是则return
Tagged Pointer是对NSNumber NSDate等的一些的优化
然后还是分两大种情况一种是没有发生借位操作,只是将引用计数单纯的进行-1操作
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
如果发生了借位,则又会分为两种情况,首先判断newisa.has_sidetable_rc是否为1,若为1则执行
if (!handleUnderflow) {//与retain作用相似 重新调用本方法(递归) rootRelease(bool performDealloc, bool handleUnderflow)并将handleUnderflow=true
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
类似于retain时的操作
然后从SideTable中借出RC_HALF这么多位,然后将这个值-1后赋值给extra_rc
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);//试图从side table中删除计数 并返回所删除的引用计数
if (borrowed > 0) {//借出的引用计数大于0
newisa.extra_rc = borrowed - 1; // redo the original decrement too
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
}
下面紧跟着防止更新失败后再一次赋值操作,如果再次失败,则将数据重新放入表中;
第二种情况,如果Side table为空,则至今进行dealloc,首先将isa的deallocating设置为true,然后直接调用dealloc方法。自此我们分析完了retain与release的实现,那dealloc的时候又做了什么呢?
老套路,先看下delloc的方法调用
- (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 &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
在rootDealloc中可以看出来,如果这个isa是优化过的并且不包含/不曾经包含weak指针且没有关联对象且没有c++的析构方法且引用计数没有超出上限的时候可以快速释放,否则调用object_dispose方法
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();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
可以看到会调用移除关联对象的方法并且调用析构函数,然后调用了clearDeallocating方法
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
这个方法判断了是否是优化过后的isa,然后调用sidetable_clearDeallocating或clearDeallocating_slow,这两个方法都是对SideTable这个hash表进行一个清理,删除引用计数与weak表
那么我们自己在类中所写的属性是什么时候被释放的呢?我们去看下,首先我们先去看看object_cxxDestruct方法
void object_cxxDestruct(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) 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->superclass) {
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());
}
(*dtor)(obj);
}
}
}
这个方法中会从自己本身的类开始寻找.cxx_destruct方法,如果找到就调用,然后一直往上级的父类找循环调用,这个方法是C++的析构方法,我们的对象都是在这被释放的,那么这个.cxx_destruct方法是怎么出现在我们的类里面的?
写两个类
@interface Person : NSObject
@property (nonatomic, copy) NSString * name;
@end
@implementation Person
@end
@interface Student : Person
@property (nonatomic, strong) NSObject * objc;
@end
@implementation Student
- (void)setObjc:(NSObject *)objc{}
- (NSObject *)objc{ return nil;}
@end
如果声明一个属性后自己手动将setter,getter方法写出来后,编译器不会将我们生成实例变量 然后测试
.cxx_destruct方法
最后在简单的说下weak吧,刚刚在看SideTable的时候可以看到,在SideTable中有RefcountMap refcnts;与weak_table_t weak_table;两个,RefcountMap我们都知道是保存引用计数的,而weak_table_t正是保存当前对象有被哪些weak指针引用了
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
}
referent==>对象地址,用于weak_entry_t 数组遍历时的比对;
联合体内struct1->weak_referrer_t *referrers;
联合体内struct2->inline_referrers[WEAK_INLINE_COUNT]
weak变量的指针个数不超过4个用inline_referrers,
weak变量的指针个数超过4个用referrers.
小计:
1,对象的引用计数都是存储在extra_rc与SideTable中;
2,对象拥有成员变量时编译器会自动插入.cxx_desctruct方法用于自动释放;
3,SideTable中保存了溢出的引用计数与weak指针
存疑:
在自身调试的时候发现如果子类的dealloc方法被调用后也会调用父类的dealloc,这是常识大家都知道,但是不理解为什么需要调用,dealloc中就解除了自己本身的关联对象,weak指针然后释放了所有成员变量,而且在释放成员变量的时候会向上找自己的父类,那么这时候调用[super dealloc]的话有什么意义吗?
已解决:调用[super dealloc]的原因是有可能父类中有在dealloc中处理一些别的事情,并且dealloc的操作是在顶级父类NSObject中实现的,如果不调用super就不会执行释放操作了(很简单的一个事情被我自己给搞的那么纠结...)
文章参考:
黑箱中的 retain 和 release
iOS Objective-C底层 part3:live^reference
iOS Objective-C底层 part4:die