天天看点

IOS开发底层dealloc释放细节探究

大家都知道ARC后是不能显示地调用dealloc方法,但是可以重载此方法。

突然考虑到如果父类有属性需要释放,而子类重写了此方法,

且重写子类dealloc时不能调用[super dealloc]会不会把父类的方法覆盖了

写了个测试类

@interface ClassA:NSObject
 
@end
 
@implementation ClassA
-(void)dealloc
{
    NSLog(@"dealloc ClassA");
}
@end
 
 
@interface ClassB:ClassA
 
@end
 
@implementation ClassB
-(void)dealloc
{
    NSLog(@"dealloc ClassB");
}
@end
 
//main方法内
 
ClassA* b = [[ClassB alloc] init];
b = nil;
           

输出结果为

2013-10-11 12:48:25.721 ARCDeallocTestDemo[2420:a0b] dealloc ClassB
2013-10-11 12:48:25.723 ARCDeallocTestDemo[2420:a0b] dealloc ClassA
           

结论:

虽然我们重写了父类的dealloc,但在编译后,编译器还是会调用到父类的dealloc,这是因为编译器会为我们插入[super dealloc],(可以查看汇编验证)所以,最终当引用计数为0的时候,的dealloc调用流程如下:

子类------>父类1------>父类2------->NSObject

最终释放的代码也是在NSObject源码中的,看下最终释放:

IOS开发底层dealloc释放细节探究

可以看到,真正的释放调用的是free,但是在此之前,需要判断是不是tagPointer,如果是tagPointer,那么需要判断:

  1. 弱引用
  2. 关联对象
  3. c++析构函数
  4. 引用计数表

如果这些都为false,那么直接free。否则如果不是tagPointer,那么调用

object_dispose(obj)---->objc_destructInstance(obj),然后调用:

IOS开发底层dealloc释放细节探究

然后调用:obj->clearDeallocating()

IOS开发底层dealloc释放细节探究

然后调用了sidetable_clearDeallocating();

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

可以看到,目的是删除这个对象所对应的引用计数表以及删除weak表,可以看到是调用了 weak_clear_no_lock(&table.weak_table, (id)this);

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry);
}
           

这里不就是将对应的指针置位nil吗,如下图:

IOS开发底层dealloc释放细节探究

所以,总结下,不管是arm64的tagPointer,还是普通的pointer,当其要销毁一个对象的时候,总是做以下几步操作:

  1. 删除这个对象所有关联对象
  2. c++析构函数
  3. 删除引用计数表中obj对应的引用计数
  4. 将弱引用表中的弱引用指针置位nil

        当一个对象的强引用被销毁的时候,其会调用objc_release(),然后一路调用,然后进行引用计数-1,当-1后引用计数为0了,那么会调用dealloc方法,否则仅仅-1。然后dealloc中会执行上面那一大坨方法来释放这个实例对象,就是这样。