美文网首页内存iOS小集
iOS进阶专项分析(十)、iOS内存的布局管理及优化

iOS进阶专项分析(十)、iOS内存的布局管理及优化

作者: 溪浣双鲤 | 来源:发表于2020-08-04 19:29 被阅读0次

    对于学习来说,最大的成本不是金钱,而是时间。低质量低效率的学习不仅是对金钱的浪费,更是对时间、生命的浪费。

    先来看一系列大厂必问的iOS的高阶面试题:

    1. 什么是ARC&MRC?底层是如何实现的?
    2. 对象调用alloc和init方法之后,引用计数是0还是1?为什么?
    3. weak实现原理以及weak指针是怎样移除的?何时移除?

    然后带着这三个问题开始本篇干货:

    1. iOS内存布局及优化技巧
    2. 内存管理机制ARC&MRC
    3. 内存管理之引用计数retain/release底层实现
    4. 内存管理之dealloc底层实现
    5. 内存管理之weak底层实现

    一、内存布局及优化


    三图看懂内存布局及优化

    1.内存布局及存储类型

    1.1内存布局.png 1.2内存布局.png

    2.内存布局方向的优化技巧

    1.3内存优化.png

    二、内存管理机制ARC&MRC


    1、内存管理机制:

    引用计数机制,创建时引用计数为1,被持有会对引用计数+1,对象不再使用或者手动release会对引用计数-1,当引用计数为0的时候由系统进行销毁。(注意释放的条件,不要和release混淆,release只是引用计数-1,而不是释放)

    引用计数管理机制:谁创建,谁释放;谁引用,谁管理。

    2、MRCARC的异同:

    MRC(全称Manual Reference Counting 手动引用计数)和ARC(全称Automatic Reference Counting,自动引用计数,iOS5推出)底层都是引用计数机制。

    ARC是编译属性,是编译器和runtime结合(对象的持有和释放)实现的结果。

    三、内存管理之引用计数retain/release底层实现


    打开Objc源码,在objc-object.h中找到这俩的实现

    retain的实现:

    // Equivalent to calling [this retain], with shortcuts if there is no override
    inline id 
    objc_object::retain()
    {
        assert(!isTaggedPointer());
    
        if (fastpath(!ISA()->hasCustomRR())) {
            return sidetable_retain();
        }
    
        return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
    }
    

    release的实现部分:

    // Equivalent to calling [this release], with shortcuts if there is no override
    inline void
    objc_object::release()
    {
        assert(!isTaggedPointer());
    
        if (fastpath(!ISA()->hasCustomRR())) {
            sidetable_release();
            return;
        }
    
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
    }
    
    

    明显看出底层分别调用了sidetable_retain()sidetable_release()

    id
    objc_object::sidetable_retain()
    {
    #if SUPPORT_NONPOINTER_ISA
        assert(!isa.nonpointer);
    #endif
        //根据对象的地址,从一大堆散列表中,获取当前对象引用计数的散列表
        SideTable& table = SideTables()[this];
        
        //自旋锁,加锁
        table.lock();
        //获取引用计数
        size_t& refcntStorage = table.refcnts[this];
        if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
            //引用计数增加
            refcntStorage += SIDE_TABLE_RC_ONE;
        }
        //自旋锁,解锁
        table.unlock();
    
        return (id)this;
    }
    
    // rdar://20206767
    // return uintptr_t instead of bool so that the various raw-isa 
    // -release paths all return zero in eax
    uintptr_t
    objc_object::sidetable_release(bool performDealloc)
    {
    #if SUPPORT_NONPOINTER_ISA
        assert(!isa.nonpointer);
    #endif
        //根据对象的地址,从大散列表中,获取当前对象引用计数的散列表
        SideTable& table = SideTables()[this];
        //定义局部变量,是否需要dealloc
        bool do_dealloc = false;
        
        //自旋锁加锁
        table.lock();
        //获取当前对象的引用计数
        RefcountMap::iterator it = table.refcnts.find(this);
        
        //判断是否需要dealloc
        if (it == table.refcnts.end()) {
            do_dealloc = true;
            table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
        } else if (it->second < SIDE_TABLE_DEALLOCATING) {
            // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
            do_dealloc = true;
            it->second |= SIDE_TABLE_DEALLOCATING;
        } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
            //如果不需要dealloc,则引用计数减1
            it->second -= SIDE_TABLE_RC_ONE;
        }
        //自旋锁解锁
        table.unlock();
        
        //如果需要dealloc则发送消息,调用SEL_dealloc
        if (do_dealloc  &&  performDealloc) {
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
        }
        return do_dealloc;
    }
    
    

    存储SideTable的全局哈希映射表StripedMap

    static StripedMap<SideTable>& SideTables() {
        return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
    }
    

    散列表SideTable的结构:

    struct SideTable {
        spinlock_t slock;//内核自旋锁spinlock_t
        RefcountMap refcnts;//引用计数字典map
        weak_table_t weak_table;//weak表
    
        SideTable() {
            memset(&weak_table, 0, sizeof(weak_table));
        }
    
        ~SideTable() {
            _objc_fatal("Do not delete SideTable.");
        }
        
        //自旋锁提供的加锁解锁方法
        void lock() { slock.lock(); }
        void unlock() { slock.unlock(); }
        void forceReset() { slock.forceReset(); }
    
        //提供给weak操作的地址顺序锁
        // Address-ordered lock discipline for a pair of side tables.
        template<HaveOld, HaveNew>
        static void lockTwo(SideTable *lock1, SideTable *lock2);
        template<HaveOld, HaveNew>
        static void unlockTwo(SideTable *lock1, SideTable *lock2);
    };
    
    

    思考:为什么Runtime会映射维护多张散列表SideTable?而不是维护一张散列表?

    回答:由于散列表中的操作会加锁(比如引用计数增加、减少都会加自旋锁),如果只有一个表的话,假如我有两个类Person,Student,那么我对Person对象进行操作的时候,再想对Student对象进行操作,就只能等待Person的操作完成解锁后才能操作,这样效率会大大下降。并且多次对同一张表进行操作,提高了使用频率,可能会造成稍微有修改就需要动整张表的不够合理的操作。

    而使用了多张表,就避免了以上的几种问题,哪个类需要处理就去改对应的表,而且避开了锁的问题,效率上也提升了很高,用空间换时间。

    分析源码总结retain和release的实现逻辑如下:

    Runtime维护了一个全局哈希映射表StripedMap,根据对象可以在全局映射表中可以获取该对象对应的散列表SideTable,该SideTable拥有三个成员变量,一个自旋锁spinlock_t,一个引用计数表RefcountMap,以及一个weak表weak_table_t。引用计数表RefcountMap以对象的地址作为key,以引用计数作为value。

    retainobjc_object在底层调用了sidetable_retain(),查找引用计数表,做了引用计数refcntStorage+=SIDE_TABLE_RC_ONE;的操作;

    releaseobjc_object在底层调用了sidetable_release(),查找引用计数表,做了引用计数refcntStorage-=SIDE_TABLE_RC_ONE;的操作;如果引用计数小于阀值SIDE_TABLE_DEALLOCATING,就调用SEL_dealloc

    图文总结如下:

    retain&release底层实现.png

    由于SideTable结构体中包含一个自旋锁,笔者此处拓展一下自旋锁相关知识:互斥锁的作用,以及和自旋锁的区别

    自旋锁是互斥锁的一种实现,互斥锁的作用就是确保同一时间只有一个线程访问数据,对资源加锁后,会等待资源解锁,在这期间会阻塞线程,直到解锁;而自旋锁则是忙等,在加锁后,会不断地去询问判断是否解锁。两者的区别主要是:在等待期间互斥锁会放弃CPU,而自旋锁会不断的循环测试锁的状态,会一直占用CPU。

    总结之后,来看一个retainCount经典的面试题:

    对象进行alloc和init之后的引用计数值为多少?到底是0还是1?为什么?

    带着问题,我们源码中全局搜索retainCount {找到实现,并进入rootRetainCount

    - (NSUInteger)retainCount {
        return ((id)self)->rootRetainCount();
    }
    
    

    rootRetainCount源码,注意代码逻辑

    inline uintptr_t 
    objc_object::rootRetainCount()
    {
        if (isTaggedPointer()) return (uintptr_t)this;
    
        //散列表SideTable提供的自旋锁,加锁
        sidetable_lock();
        isa_t bits = LoadExclusive(&isa.bits);
        ClearExclusive(&isa.bits);
        
        //判断bits.nonpointer
        if (bits.nonpointer) {
            //这里先直接+1
            uintptr_t rc = 1 + bits.extra_rc;
            //判断有没有使用sideTable存储引用计数,如果有,就加上
            if (bits.has_sidetable_rc) {
                rc += sidetable_getExtraRC_nolock();
            }
            //散列表自旋锁解锁
            sidetable_unlock();
            
            //没有就直接返回,
            return rc;
        }
        
        //散列表SideTable提供的自旋锁,解锁
        sidetable_unlock();
        
        //直接返回sidetable_retainCount
        return sidetable_retainCount();
    }
    
    

    下面是sidetable_retainCount的实现

    uintptr_t
    objc_object::sidetable_retainCount()
    {
        SideTable& table = SideTables()[this];
        //设置初始值1
        size_t refcnt_result = 1;
        
        table.lock();
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it != table.refcnts.end()) {
            // this is valid for SIDE_TABLE_RC_PINNED too
            refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
        }
        table.unlock();
        return refcnt_result;
    }
    
    

    打上断点,对象创建走的是bits.nonpointer为1的逻辑,此时bits.extra_rc存储的引用计数为0,但是引用计数rc = 1 + bits.extra_rc,所以返回了1

    从这个源码中我们又看出一件事,引用计数不一定存储在哈希表中,还有可能存储在类isa的
    bits.extra_rc中,找到isa的结构isa_t以及isa_t的结构中找到宏定义ISA_BITFIELD笔者把宏定义整理了一下,注意其中的
    has_sidetable_rcextra_rc

    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    #if defined(ISA_BITFIELD)
        struct {
            uintptr_t nonpointer        : 1;  // 0:普通指针,1:优化过,使用位域存储更多信息
        uintptr_t has_assoc         : 1;  // 对象是否含有或曾经含有关联引用
        uintptr_t has_cxx_dtor      : 1;  // 表示是否有C++析构函数或OC的dealloc
        uintptr_t shiftcls          : 33; // 存放着 Class、Meta-Class 对象的内存地址信息
        uintptr_t magic             : 6;  // 用于在调试时分辨对象是否未完成初始化
        uintptr_t weakly_referenced : 1;  // 是否被弱引用指向
        uintptr_t deallocating      : 1;  // 对象是否正在释放
        uintptr_t has_sidetable_rc  : 1;  // 是否需要使用 sidetable 来存储引用计数
        uintptr_t extra_rc          : 19;  // 引用计数能够用 19 个二进制位存储时,直接存储在这里
        };
    #endif
    };
    

    经过分析,isa_t里面的extra_rc也是用来存储引用计数的,只不过大小有限,如果超过了大小,就会存储到散列表SideTable中。

    总结刚才的答案如下:

    问题:对象进行alloc和init之后的引用计数值为多少?到底是0还是1?为什么?

    回答:对象alloc之后,在引用计数表中的引用计数其实为0,只是在获取retainCount的方法rootRetainCount的内部进行了+1的操作

    总结对象生命周期引用计数的变化图如下:

    retainCount流程.png

    四、内存管理之dealloc底层实现


    直接进入源码搜索dealloc {,找到底层函数调用流程如下:

    1. dealloc底层调用_objc_rootDealloc()
    2. _objc_rootDealloc()调用objc_object::rootDealloc()
    3. objc_object::rootDealloc()调用object_dispose()
    4. object_dispose()进行了free(obj)释放对象,同时调用objc_destructInstance()
    5. objc_destructInstance()函数中判断是否有析构函数和关联引用,如果有,就要移除,最后调用clearDeallocating()
    6. clearDeallocating()中进行引用计数refcnt的清除和weak指针的移除,并调用weak_clear_no_lock()(这个weak指针移除具体步骤在下面的weak指针清除的时候进行详细分析。)

    下面贴出上述步骤的所有源码:

    1、dealloc底层调用_objc_rootDealloc()

    // Replaced by NSZombies
    - (void)dealloc {
        _objc_rootDealloc(self);
    }
    
    

    2、_objc_rootDealloc()调用objc_object::rootDealloc()

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

    3、objc_object::rootDealloc()调用object_dispose()

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

    4、object_dispose()进行了对象的释放free(obj),同时调用objc_destructInstance()

    id 
    object_dispose(id obj)
    {
        if (!obj) return nil;
    
        objc_destructInstance(obj);  
        //释放obj  
        free(obj);
    
        return nil;
    }
    

    5、在objc_destructInstance()函数中判断是否有析构函数和关联引用,如果有,就要移除,最后调用clearDeallocating()

    /***********************************************************************
    * objc_destructInstance
    * Destroys an instance without freeing memory. 
    * Calls C++ destructors.
    * Calls ARC ivar cleanup.
    * Removes associative references.
    * Returns `obj`. Does nothing if `obj` is 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;
    }
    
    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();
    }
    
    
    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主要做了下面这些内容:

    1. 首先判断并调用对象C++的析构函数,释放对象占用的空间,顺序是:先子类->后父类 ----- object_cxxDestruct()
    2. 然后清除对象关联引用 ----- _object_remove_assocations()
    3. 然后从weak表中清空并移除对象对应的所有weak指针 -----weak_clear_no_lock()
    4. 然后移除引用计数 ----- table.refcnts.erase(it);
    5. 最后释放对象 free(obj)

    对应流程图总结如下:

    dealloc流程图.png

    五、内存管理之weak底层实现


    分析完retain/release以及dealloc的底层实现,我们来继续分析weak的底层实现及weak表的插入和移除操作:

    当我们开发时用__weak修饰变量,其实Runtime会在底层调用objc_initWeak函数,objc_initWeak()底层又调用了storeWeak()storeWeak()的作用就是向表中注册弱引用指针,或者更新表,下面是storeWeak()的实现部分,笔者把注释写在里面:

    /** 
     * 注意注释部分!!!注释已经说的很直白了
     * Initialize a fresh weak pointer to some object location. 
     * It would be used for code like: 
     *
     * (The nil case) 
     * __weak id weakPtr;
     * (The non-nil case) 
     * NSObject *o = ...;
     * __weak id weakPtr = o;
     * 
     * This function IS NOT thread-safe with respect to concurrent 
     * modifications to the weak variable. (Concurrent weak clear is safe.)
     *
     * @param location Address of __weak ptr. 
     * @param newObj Object ptr. 
     */
    id
    objc_initWeak(id *location, id newObj)
    {
        
        if (!newObj) {
            *location = nil;
            return nil;
        }
    
        return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object*)newObj);
    }
    
    
    static id 
    storeWeak(id *location, objc_object *newObj)
    {
        assert(haveOld  ||  haveNew);
        if (!haveNew) assert(newObj == nil);
    
        Class previouslyInitializedClass = nil;
        id oldObj;
        SideTable *oldTable;
        SideTable *newTable;
        //
    
        // Acquire locks for old and new values.
        获取旧值和新值的锁。
        // Order by lock address to prevent lock ordering problems. 
        按锁地址排序,以防止出现锁排序问题。
        // Retry if the old value changes underneath us.
        如果旧值在之后改变就重试操作
     retry:
        //获取旧值和新值
        if (haveOld) {
            oldObj = *location;
            oldTable = &SideTables()[oldObj];
        } else {
            oldTable = nil;
        }
        if (haveNew) {
            newTable = &SideTables()[newObj];
        } else {
            newTable = nil;
        }
        //加锁
        SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    
        if (haveOld  &&  *location != oldObj) {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            goto retry;
        }
        //
        // Prevent a deadlock between the weak reference machinery
        // and the +initialize machinery by ensuring that no 
        // weakly-referenced object has an un-+initialized isa.
        //防止初始化机制和弱引用机制之间出现死锁
        
        if (haveNew  &&  newObj) {
            Class cls = newObj->getIsa();
            if (cls != previouslyInitializedClass  &&  
                !((objc_class *)cls)->isInitialized()) 
            {
                SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
                _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
    
                // If this class is finished with +initialize then we're good.
                // If this class is still running +initialize on this thread 
                // (i.e. +initialize called storeWeak on an instance of itself)
                // then we may proceed but it will appear initializing and 
                // not yet initialized to the check above.
                // Instead set previouslyInitializedClass to recognize it on retry.
                
                previouslyInitializedClass = cls;
    
                goto retry;
            }
        }
    
        // Clean up old value, if any.
        //分配新值之前,清除旧值
        if (haveOld) {
            weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
        }
    
        // Assign new value, if any.
        // 分配新值
        if (haveNew) {
            newObj = (objc_object *)
                weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                      crashIfDeallocating);
            // weak_register_no_lock returns nil if weak store should be rejected
    
            // Set is-weakly-referenced bit in refcount table.
            if (newObj  &&  !newObj->isTaggedPointer()) {
                newObj->setWeaklyReferenced_nolock();
            }
    
            // Do not set *location anywhere else. That would introduce a race.
            *location = (id)newObj;
        }
        else {
            //没有新值就不改
            // No new value. The storage is not changed.
        }
        
        //解锁
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    
        return (id)newObj;
    }
    
    

    然后我们来看一下SideTable中weak表weak_table_t的源码:

    /**
     * The global weak references table. Stores object ids as keys,
     * and weak_entry_t structs as their values.
     */
    struct weak_table_t {
        //保存了所有指向指定对象的 weak 指针
        weak_entry_t *weak_entries;
        //存储空间
        size_t    num_entries;
        //引用计数辅助量
        uintptr_t mask;
        //最大偏移值
        uintptr_t max_hash_displacement;
    };
    
    

    从之前引用计数的分析以及weak表的源码中分析得出:Runtime维护了一个全局哈希映射表StripedMap,不同对象映射着对应的散列表SideTable,散列表中包含引用计数map以及weak弱引用表weak_table_tweak_table_t中保存了所有指定对象的weak指针,用对象的地址作为key,weak_entry_t结构体对象作为value;weak_entry_t负责维护和存储指向一个对象的所有弱引用的散列表。

    weak_entry_t源码:

    /**
     * The internal structure stored in the weak references table. 
     * It maintains and stores
     * a hash set of weak references pointing to an object.
     * If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set
     * is instead a small inline array.
     */
    
    typedef DisguisedPtr<objc_object *> weak_referrer_t;
    struct weak_entry_t {
        DisguisedPtr<objc_object> referent;
        union {
            struct {
                weak_referrer_t *referrers;
                uintptr_t        out_of_line_ness : 2;
                uintptr_t        num_refs : PTR_MINUS_2;
                uintptr_t        mask;
                uintptr_t        max_hash_displacement;
            };
            struct {
                // out_of_line_ness field is low bits of inline_referrers[1]
                weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
            };
        };
    
        bool out_of_line() {
            return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
        }
        
        weak_entry_t& operator=(const weak_entry_t& other) {
            memcpy(this, &other, sizeof(other));
            return *this;
        }
    
        weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
            : referent(newReferent)
        {
            inline_referrers[0] = newReferrer;
            for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
                inline_referrers[i] = nil;
            }
        }
    };
    
    

    weak表的存储结构思维导图总结如下:

    weak底层存储结构.png

    针对weak_table_t还提供了三个主要的方法:向表中添加元素的方法weak_register_no_lock、从表中移除元素的weak_unregister_no_lock、以及清空weak指针weak_clear_no_lock

    向添加weak_table_t中添加方法weak_register_no_lock的源码:

    /** 
     * Registers a new (object, weak pointer) pair. Creates a new weak
     * object entry if it does not exist.
     * 
     * @param weak_table The global weak table.
     * @param referent The object pointed to by the weak reference.
     * @param referrer The weak pointer address.
     */
    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;
        //weak指针地址
        objc_object **referrer = (objc_object **)referrer_id;
    
        ......
        
        // 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实体
            //根据对象及弱指针生成一个weak_entry_t结构体对象,并插入表中
            weak_entry_t new_entry(referent, referrer);
            weak_grow_maybe(weak_table);
            //插入到表中
            weak_entry_insert(weak_table, &new_entry);
        }
    
        return referent_id;
    }
    
    
    

    移除方法weak_unregister_no_lock的源码:

    void
    weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                            id *referrer_id)
    {
        objc_object *referent = (objc_object *)referent_id;
        objc_object **referrer = (objc_object **)referrer_id;
    
        weak_entry_t *entry;
    
        if (!referent) return;
    
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            //移除表中的weak指针
            remove_referrer(entry, referrer);
            
            ......
    
            if (empty) {
                //移除weak_table表中对应的的entry
                weak_entry_remove(weak_table, entry);
            }
        }
    }
    
    

    清空对象的weak指针,调用时机就是对象dealloc的时候:

    /** 
     * 调用时机:
     * Called by dealloc; nils out all weak pointers that point to the 
     * provided object so that they can no longer be used.
     * 
     * @param weak_table 
     * @param referent The object being deallocated. 
     */
    void 
    weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
    {
        objc_object *referent = (objc_object *)referent_id;
        //获取weak_entry_t
        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;
        }
        //获取weak_entry_t中的weak_referent_t,遍历weak_referrer_t,将其中的weak指针置为nil
        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);
    }
    
    

    总结一下:

    1、weak底层实现的流程

    1. Runtime全局维护了一个全局映射表StripedMap,根据对象的地址能够获取对应的散列表SideTable(注意!!!也有可能是多个对象共用一个散列表),散列表SideTable之中包含有weak表weak_table_tweak_table_t中根据对象的地址能够查到该对象对应的weak_entry_t实体,weak_entry_t用来管理对象的所有的weak指针,weak指针存储在weak_referrer_t中。

    2. 当我们在用__weak修饰对象的时候,运行时Runtime会在底层调用objc_initWeak()方法

    3. objc_initWeak()方法会调用storeWeak()

    4. storeWeak()这个函数会先判断对象是否初始化,如果未初始化,则进行对象初始化,然后创建对应的SideTable;如果对象已经有SideTable,那么判断weak指针是否需要更新,更新操作就是删除对应location位置的weak_entry_t对象,创建新的weak_entry_t,然后插入到weak表weak_table_t中。

    2、weak指针移除原理

    1、移除时机:调用对象的dealloc方法时,中间会调用clearDeallocating,其中会调用weak_clear_no_lock对weak指针进行移除。

    2、移除原理:weak_clear_no_lock底层会获取weak表weak_table_t中的实体weak_entry_t,然后拿到其中的weak_referrer_t,拿到weak_referrer_t之后,遍历并将其中的所有weak指针置为nil,最后把这个weak_entry_tweak_table_t中移除。

    3、weak指针本质:从源码中可以看出weak指针的类型为是objc_object**,是对象的二维指针,就是指向对象地址的指针。

    接下来看一个问题:

    如果在dealloc中使用__weak会有什么样的结果?

    答案:会crash!

    回到源码,在storeWeak函数源码的上边找到下面这部分代码

    // Update a weak variable.
    // If HaveOld is true, the variable has an existing value 
    //   that needs to be cleaned up. This value might be nil.
    // If HaveNew is true, there is a new value that needs to be 
    //   assigned into the variable. This value might be nil.
    // If CrashIfDeallocating is true, the process is halted if newObj is 
    //   deallocating or newObj's class does not support weak references. 
    //   If CrashIfDeallocating is false, nil is stored instead.
    
    enum CrashIfDeallocating {
        DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
    };
    template <HaveOld haveOld, HaveNew haveNew,
              CrashIfDeallocating crashIfDeallocating>
              
    

    以及注册weak指针的方法

    /** 
     * Registers a new (object, weak pointer) pair. Creates a new weak
     * object entry if it does not exist.
     * 
     * @param weak_table The global weak table.
     * @param referent The object pointed to by the weak reference.
     * @param referrer The weak pointer address.
     */
    id 
    weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                          id *referrer_id, bool crashIfDeallocating)
    {
        
        ......
        
        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;
            }
        }
    
        ......
    
        return referent_id;
    }
    

    结合第一部分注释部分以及第二部分的注册函数weak_register_no_lock进行分析:如果一个对象正在进行dealloc的时候,进行weak指针的更新操作,就会直接crash!并报错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.

    注册时这么判断作用就是:在对象正在释放的过程中,或者对象已经释放后,是不允许使用weak来引用实例变量的。这样就是为了防止野指针的出现。

    -END-

    相关文章

      网友评论

        本文标题:iOS进阶专项分析(十)、iOS内存的布局管理及优化

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