RunTime源码阅读(三)dealloc的释放

2020-01-22  本文已影响0人  某非著名程序员

dealloc的释放颇具色彩:因为OC的结构,弱引用,关联对象,C++等,所以需要分开释放。

1. dealloc

//NSObject.mm
- (void)dealloc {
    _objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
    assert(obj);
    obj->rootDealloc();
}

//objc-object.h
inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?
    //开启了指针优化、没有弱引用计数、没有关联对象、没有C++、没有sidetabled的引用计数
    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);//弱引用计数、关联对象、C++、或开启了sidetabled的引用计数
    }
}

  1. 小对象类型使用的是taggedPointer,直接存储的值,因此不需要释放。
  2. isa.weakly_referenced weak类型标志位、has_assoc关联对象标志位、has_cxx_dtor C++标志位、has_sidetable_rc 引用计数有没有借用sidetable标志位。
  3. 释放分两种:如果2中情况都没有,free(this)释放当前对象,否则调用object_dispose进行释放。
    因为weak、关联对象、C++、sidetable存储的结构不同,所以不同类型需要分开释放。
//objc-runtime-new.mm
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();//C++
        bool assoc = obj->hasAssociatedObjects();//关联对象

        // This order is important.
        if (cxx) object_cxxDestruct(obj);// 调用C++析构函数
        if (assoc) _object_remove_assocations(obj);// 移除所有的关联对象,并将其自身从Association Manager的map中移除
        obj->clearDeallocating();// 清理相关的引用
    }

    return obj;
}

2. C++释放

//objc-class.mm
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);//调回.cxx_destruct的释放方法,释放必须交给用户
        }
    }
}

dtor = (void(*)(id))
lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);这一段与调用objc_msgsend方法类似。

void (*action)(id, SEL, NSString*) = (void (*)(id, SEL, NSString*))objc_msgSend;
action(self, @selector(SendImage:), fileName);

(*dtor)(obj);可以理解为调用了C++的析构方法,当前cls释放,superclass释放。

3. 关联对象释放

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

RunTime源码阅读(二)关联对象这篇文章介绍过,一个对象对应一个AssociationsHashMap。因此释放掉AssociationsHashMap即可。
delete refs;删除ObjectAssociationMap对象,associations.erase(i);

ReleaseValue释放OBJC_ASSOCIATION_SETTER_RETAIN的value。

for_each(elements.begin(), elements.end(), ReleaseValue());

struct ReleaseValue {
    void operator() (ObjcAssociation &association) {
        releaseValue(association.value(), association.policy());
    }
};

static void releaseValue(id value, uintptr_t policy) {
    if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
        return objc_release(value);
    }
}

4.弱引用与引用计数相关

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

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

看sidetable数据结构,同时管理弱引用与计数。所以释放可以放在一起。
weak_clear_no_lock是弱引用的释放。

4.1 弱引用释放

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);// 找到referent在weak_table中对应的weak_entry_t
    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;
    // 找出weak引用referent的weak 指针地址数组以及数组长度
    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];// 取出每个weak ptr的地址
        if (referrer) {
            if (*referrer == referent) {// 如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil的原因
                *referrer = nil;
            }
            else if (*referrer) {// 如果所存储的weak ptr没有weak 引用referent,这可能是由于runtime代码的逻辑错误引起的,报错
                _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);
}

具体释放源码,参考RunTime源码阅读(一)之weak
由于weak的存储时分动态数组与静态数组,释放时需要区分。另外所谓的动态数组是判断当实际个数与总个数的大小进行比较,做扩容与收容操作。保证空间高效利用。

4.2 引用计数的释放

在开启isa指针优化后,大部分是用不到sidetable存储的。苹果分配了19位来存储extra_rc,也就是2^19-1= 524 287,大部分是够用的。
释放table.refcnts.erase(it);

总结:

  1. 整个释放过程还是比较复杂。建议还是跟着源码慢慢品读吧!
  2. 有任何问题欢迎留言评论
上一篇下一篇

猜你喜欢

热点阅读