13

带你深入了解OC对象的销毁过程

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzUyMDAxMjQ3Ng%3D%3D&%3Bmid=2247495339&%3Bidx=1&%3Bsn=585ebae03dc214c594a332f1cccf12dd
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在前一篇文章中给大家已经介绍过 OC对象的创建过程 ,这次带大家了解一下OC对象的释放dealloc过程。

我们都知道当对象经历其生命周期后,最终会被系统所回收,就会调用dealloc方法进行析构。在手动引用计数(MRC)时代,内存管理需要程序员手动处理,去除对象引用通过手动调用relase方式进行。调用方式如下:

-(void)dealloc
{
/*
释放自身的实例变量,
移除观察者,
停止timer,
移除通知,
代理置空等。
*/
[super dealloc];
}

而ARC时代我们除了非Cocoa对象需要我们手动处理外,其他如实例释放和[supper dealloc]都不需要处理了,调用方式为:

-(void)dealloc
{
/*
非oc对象释放,例如CF对象
*/
}

dealloc使用注意项

上面介绍了dealloc的在MRC和ARC下的不同调用,那么在平时开发过程中我们应该注意什么呢?

1、 dealloc中使用__weak

- (void)dealloc
{
__weak __typeof(self)weak_self = self;
NSLog(@"%@", weak_self);
}

上面代码在调用过程中当执行到dealloc时候,程序会crash,崩溃信息显示为: Cannot form weak reference to instance (0x600000450730) of class Person. It is possible that this object was over-released, or is in the process of deallocation。 查看堆栈信息堆栈崩在了objc_initWeak中,查看源码objc_initWeak函数的定义是:

id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}

其中内部还调用了storeWeak函数,查看源码可以看到有个枚举值CrashIfDeallocating,当调用到storeWeak函数可以知道如果在释放过程中存储,程序就会crash,最终调用weak_register_no_lock(),在该函数中可以查找到崩溃的的信息。

id
objc_storeWeak(id *location, id newObj)
{
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object *)newObj);
}

id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;


if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}




if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
// now remember it and where it is being stored
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}

2、 dealloc中尽量直接访问实例变量来置空

3、 不要在dealloc中随便调用其他方法,尤其线程操作避免使用GCD,可以利用performSelector确保线程操作先于释放过程。

那么ARC下对象的实例变量是如何释放的呢?我们就从dealloc源码分析中查看吧。

NSObject中Dealloc的调用流程

  • 首先调用_objc_rootDealloc方法

// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
  • 调用rootDealloc()方法

Void _objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
  • 最终调用到objc_object.h的内联函数inline void objc_object::rootDealloc()函数中

inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?




if (fastpath(isa.nonpointer && //!! 是否是优化过的isa
!isa.weakly_referenced && //!! 不包含或者不曾经包含weak指针
!isa.has_assoc && //!! 没有关联对象
!isa.has_cxx_dtor && //!! 没有c++析构方法
!isa.has_sidetable_rc)) //!! 引用计数没有超过上限的时候可以快速释放
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}

如果对象采用了Tagged Pointer技术就直接return,nonpointer代表是否对isa指针开启指针优化,0:纯isa指针,1:不止是类对象地址。 Weakly_referenced标志对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以快速的释放。 has_assoc表示关联对象标志位,0: 不存在关联对象,1: 存在关联对象。 has_cxx_dtor表示该对象是否有c++或者oc等析构方法,如果有需要做析构逻辑,如果没有可以更快的释放对象。 has_sidetable_rc表示当对象的引用计数大于10时则需要借助该变量进行存储进位,如果没有进位存储,可以更快的释放对象。 当调用free(this)时,则代表以上判断条件都符合,则会调用C函数对对象进行释放。 否则就会调用object_dispose((id)this);

  • 调用在objc-runtime-new.mm 中的object_dispose()

id 
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
  • 调用objc_destructInstance(obj)

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);//!!调用c++析构函数
if (assoc) _object_remove_assocations(obj);//!!删除关联引用
obj->clearDeallocating();//!!调用ARC ivar清理
}
return obj;
}

从调用该函数可以看到销毁一个对象需要底层c++的析构函数完成,还需要移除associative的引用,下面就详细看一下销毁对象的三个方法: object_cxxDestruct(obj)、_object_remove_assocations(obj)、obj->clearDeallocating();

a. object_cxxDestruct:从子类开始沿着继承链一直找到父类向上搜寻SEL_cxx_destruct这个Selector,找到这个函数实现并执行。

void object_cxxDestruct(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
object_cxxDestructFromClass(obj, obj->ISA());
}

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);
}
}
}

b. _object_remove_assocations:删除关联引用

其过程是先创建一个vector,接下来实例一个AssociationsManager对象,通过AssociationsManager对象获取到AssociationsHashMap对象,然后通过当前对象获取disguised_ptr_t值,然后在AssociationsHashMap中通过disguised_ptr_t对象找到ObjectAssociationMap,再遍历ObjectAssociationMap,找到对应的对象值,放入到刚开始创建的vector中,然后释放ObjectAssociationMap对象,最后将关联对象从associations中移除,最终将vector中的对象释放掉。

void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}

c. clearDeallocating()调用ARC ivar清理

clearDeallocating这里涉及两个clear函数。

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());
}

我们先看一下sidetable_clearDeallocating,在该函数中会执行weak_clear_no_lock,将指向该对象的弱引用指针置为nil,接着会调用table.refcnts.eraser(),从引用计数表中去除该对象的引用计数。

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();
}

然后看一下clearDeallocating_slow,当weakly_referenced代表对象被指向或者曾经指向一个ARC的弱变量,has_sidetable_rc判断该对象的引用计数是否过大中一个为yes时就会调用clearDeallocating_slow方法。clearDeallocating_slow方法最终也会调weak_clear_no_lock方法。如果存在引用计数过大也会从引用计数表中去除该对象引用计数,最终完成dealloc释放过程。

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();
}

dealloc整个的释放流程图如下:

ZBNfqaQ.png!mobile

对象dealloc主要流程总结

1、 object_cxxDestruct:调用该函数执行C++析构函数,释放变量。

2、 _object_remove_assocations:调用该函数删除关联引用。

3、 clearDeallocating:调用该函数中的weak_clear_no_lock函数将指向该对象的弱引用指针置为nil,调用table.refcnts.erase()从引用计数表中将该对象的引用计数去除。

4、 free():最终调用函数free()释放内存空间。

参考资料:

1、 https://opensource.apple.com/source/objc4/objc4-756.2/

2、 http://clang.llvm.org/docs/AutomaticReferenceCounting.html#weak-unavailable-types

3、 https://opensource.apple.com/source/clang/clang-211.10.1/src/tools/clang/docs/AutomaticReferenceCounting.html#runtime.objc_initWeak


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK