dealloc底层解析

前言

我们知道一个对象即将释放的时候会进入到dealloc方法中,通常也是通过dealloc是否回调来检测循环引用。我一直对dealloc内部做了哪些事情十分好奇,今天就来探究下它内部的实现机制。

源码探究

苹果的内存管理是通过引用计数实现的,当一个对象的引用计数为0时就去释放这个对象,此时dealloc触发。在触发dealloc之前,肯定是先对一个对象进行了release操作,我们先来看下release源码。

- (oneway void)release {
    ((id)self)->rootRelease();
}   

内部只是简单的调用了c++类的rootRelease方法,直接来到最终的调用方法。

ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;
    
    ... //省略中间的无关代码
 
    // 这里是准备触发dealloc的逻辑,请关注下面的代码

    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __sync_synchronize();
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return true;
}   

为了方便阅读,我去除了中间无关触发dealloc的逻辑。首先判断该对象是否正在析构,如果是,则表明该对象过度释放,程序崩溃报错,一般在mrc时代容易出现过度释放的问题。如果不是正在析构,则标记该对象的deallocting为true。接下来通过objc_msgSend方法去调用dealloc方法,按理来说通过objc_msgSend调用方法,如果在子类中找到了sel,那么父类的dealloc不会调用,在我实际测试中,dealloc的调用顺序是子类->父类->根类(NSObject),查阅资料发现是编译器插入相关代码,向父类调用dealloc,所以在dealloc中不需要[super dealloc]就能完成整个继承链的dealloc。

接下来看NSObject中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  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

可以看到执行过程比较简单,_objc_rootDealloc -> rootDealloc -> objc_object::rootDealloc,在objc_object::rootDealloc中判断对象的几个条件:

  1. isa.nonpointer,32位系统和64位系统isa的结构不同,为0表示isa直接指向对象的class,为1表示isa不是直接指向class
  2. 是否有注册weak引用表
  3. 是否有association关联属性
  4. 是否有c++析构
  5. 是否有引用计数表。

我们来看下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;
}

在object_dispose中调用objc_destructInstance主要做了以下操作:

  1. object_cxxDestruct,这里主要是用于释放对象的实例变量
  2. _object_remove_assocations,移除掉所有关联属性,即通过objc_setAssociatedObject添加的关联属性
  3. clearDeallocating,先清空weak变量表且将所有weak引用指向nil,然后清空引用计数表。

最后通过free函数清除对象内存。

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,447评论 0 9
  • 29.理解引用计数 Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数...
    Code_Ninja阅读 1,286评论 1 3
  • 1.1 什么是自动引用计数 概念:在 LLVM 编译器中设置 ARC(Automaitc Reference Co...
    __silhouette阅读 4,446评论 1 17
  • 终于,还是从微博上爆发了! 就在下午3点多的时候。 知道结局,但不知道原来有咁多支线,并不是想象中,结局就可以了!...
    DeathKnightR阅读 75评论 0 0
  • 我最初的梦想是做一个画家。 还未上小学的时候,新白娘子传奇正播得火热,我看了第一眼就爱上了,还是在别人家看的,...
    和他共剪西窗烛阅读 249评论 0 1