dealloc源码解读
isa指针
在看dealloc源码之前, 首先要了解isa指针
- 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址,
- 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
};
#endif
};
-
nonpointer
1.代表普通的指针,存储着Class、Meta-Class对象的内存地址
2.代表优化过,使用位域存储更多的信息 -
has_assoc
是否有设置过关联对象,如果没有,释放时会更快 -
has_cxx_dtor
是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快 -
shiftcls
存储着Class、Meta-Class对象的内存地址信息 -
magic
用于在调试时分辨对象是否未完成初始化 -
weakly_referenced
是否有被弱引用指向过,如果没有,释放时会更快 -
deallocating
对象是否正在释放 -
extra_rc
里面存储的值是:引用计数器减1 -
has_sidetable_rc
1.引用计数器是否过大无法存储在isa中
2.如果为1,那么引用计数会存储在一个叫SideTable的类的属性中,(如果引用计数19位不够存储的话,会存储在SideTable中)
引用计数如何存储:
引用计数可以直接存储在优化过的isa指针中,如果不够的话,会存储在SideTable
类中的RefcountMap散列表中去
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
}
引用计数+1(retainCount)
的过程
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this; //如果是TaggedPointer指针,不是oc对象
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) { //是优化过的isa指针
uintptr_t rc = 1 + bits.extra_rc; //引用计数+1
if (bits.has_sidetable_rc) { // 引用计数不是存储在isa中,而是存储在sidetable中
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
size_t
objc_object::sidetable_getExtraRC_nolock()
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);//获取引用计数列表的遍历器
if (it == table.refcnts.end()) return 0;
else return it->second >> SIDE_TABLE_RC_SHIFT;
}
引用计数-1(retainCount)
的过程
// Replaced by ObjectAlloc
- (oneway void)release {
((id)self)->rootRelease();
}
ALWAYS_INLINE bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {//不是优化过的isa指针
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
......
}
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
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) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
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) {//执行dealloc
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
dealloc执行过程
当一个对象要释放时,会自动调用dealloc,接下的调用轨迹是
dealloc
_objc_rootDealloc
rootDealloc
object_dispose
objc_destructInstance、free
源码如下
// Replaced by NSZombies
- (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指针(isa.nonpointer)
没有被弱引用指向 (!isa.weakly_referenced)
没有设置过关联对象(!isa.has_assoc)
没有C++的析构函数(!isa.has_cxx_dtor)
没有SideTable(!isa.has_sidetable_rc)
会直接释放掉free(this);
如果不满足以上任何一条,会执行 object_dispose
/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is 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(); //将指向当前对象的弱指针置为nil
}
return obj;
}
object_dispose
方法中首先会调用 objc_destructInstance
,再释放对象free(obj);
objc_destructInstance
里面会做这些事情:
- 判断对象是否有成员变量,如果有,清除成员变量
bool cxx = obj->hasCxxDtor();
if (cxx) object_cxxDestruct(obj);
- 判断是否有关联对象,如果有,移除关联对象
bool assoc = obj->hasAssociatedObjects();
if (assoc) _object_remove_assocations(obj);
- 调用
clearDeallocating
将指向当前对象的弱指针置为nil
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 ()
如果对象被弱引用指向过,或者引用计数存在sidetable中,执行clearDeallocating_slow();
void
objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
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();
}
NEVER_INLINE 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();
}
面试题
1.week指针实现原理
将弱引用存储在哈希表里,当对象销毁的时候,将当前对象的弱引用表清除掉
2.ARC 都帮我们做了什么?
ARC是LLVM编译器和Runtime系统相互协作的一个结果,LLVM编译器帮我们生成管理相关的代码,Runtime帮我处理弱引用这种操作