美文网首页
RunTime源码阅读(三)dealloc的释放

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

作者: 某非著名程序员 | 来源:发表于2020-01-22 10:24 被阅读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. 有任何问题欢迎留言评论

    相关文章

      网友评论

          本文标题:RunTime源码阅读(三)dealloc的释放

          本文链接:https://www.haomeiwen.com/subject/swxczctx.html