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

在前一篇文章中给大家已经介绍过


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整个的释放流程图如下:

对象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

京东零售技术
我还没有学会写个人说明!
上一篇

文旅部:对丁真等旅游网红加强合理引导

下一篇

LG电子将与麦格纳成立一家合资公司 生产电动汽车零部件

你也可能喜欢

评论已经被关闭。

插入图片