美文网首页iOS-机制
iOS-内存管理(二)-引用计数

iOS-内存管理(二)-引用计数

作者: xxxxxxxx_123 | 来源:发表于2020-03-22 00:46 被阅读0次

    Objective-C内存管理的核心思想就是通过对象的引用计数来对内存对象的生命周期进行控制。说直白一点,就是调用retain会加1,调用release就会减1,引用计数清零或者调用dealloc就销毁。

    引用计数

    引用计数,即为对象被持有的次数。是内存管理的核心点。下面我们来看一个关于引用计数的例子:

    - (void)testRefCount {
        NSObject *obj = [NSObject alloc];
        NSLog(@"==refCount==%ld==", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
    }
    

    运行程序,结果为1。可是alloc的流程中并没有对引用计数操作的流程,那么这个打印为什么是1呢?来看看retainCount的源码:

    - (NSUInteger)retainCount {
        return ((id)self)->rootRetainCount();
    }
    
    inline uintptr_t 
    objc_object::rootRetainCount()
    {
        if (isTaggedPointer()) return (uintptr_t)this;
    
        sidetable_lock();
        isa_t bits = LoadExclusive(&isa.bits);
        ClearExclusive(&isa.bits);
        if (bits.nonpointer) {
            uintptr_t rc = 1 + bits.extra_rc;
            if (bits.has_sidetable_rc) {
                rc += sidetable_getExtraRC_nolock();
            }
            sidetable_unlock();
            return rc;
        }
    
        sidetable_unlock();
        return sidetable_retainCount();
    }
    

    可以看到,引用计数的总值就是isa里面extra_rc的取值和散列表中引用计数表的取值外加1,我们新alloc的对象,引用计数打印为1就是因为这个加的这个1,其实新alloc出来的对象引用计数为0。

    那么引用计数是存储在哪里的,retainrelease到底是如何处理的呢?下面我们先来看一下retain

    retain

    id 
    objc_retain(id obj)
    {
        if (!obj) return obj;
        if (obj->isTaggedPointer()) return obj;
        return obj->retain();
    }
    
    inline id 
    objc_object::retain()
    {
        assert(!isTaggedPointer());
    
        if (fastpath(!ISA()->hasCustomRR())) {
            return rootRetain();
        }
    
        return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
    }
    

    可以看到,调用objc_retain首先会判断是否是isTaggedPointer,如果是就直接返回。接着会判断对象没有自定义retain/release方法就会调用rootRetain,否则就通过objc_msgSend发送SEL_retain消息。

    id 
    objc_object::rootRetain()
    {
        return rootRetain(false, false);
    }
    
    id 
    objc_object::rootRetain(bool tryRetain, bool handleOverflow)
    {
        if (isTaggedPointer()) return (id)this;
    
        bool sideTableLocked = false;
        bool transcribeToSideTable = false;
    
        isa_t oldisa;
        isa_t newisa;
    
        do {
            transcribeToSideTable = false;
            // 获取isa
            oldisa = LoadExclusive(&isa.bits);
            newisa = oldisa;
            if (slowpath(!newisa.nonpointer)) {
                // 不是nonpointer isa 散列表处理
                ClearExclusive(&isa.bits);
                if (!tryRetain && sideTableLocked) sidetable_unlock();
                if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
                else return sidetable_retain();
            }
            // 如果正在deallocating 不做处理
            if (slowpath(tryRetain && newisa.deallocating)) {
                ClearExclusive(&isa.bits);
                if (!tryRetain && sideTableLocked) sidetable_unlock();
                return nil;
            }
            
            // 是nonpointer isa  extra_rc++
            uintptr_t carry;
            newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
    
            if (slowpath(carry)) {
                // newisa.extra_rc++ overflowed
                if (!handleOverflow) {
                    ClearExclusive(&isa.bits);
                    return rootRetain_overflow(tryRetain);
                }
                // 超出之后一半存到散列表中,一半放在extra_rc 并且处理isa的extra_rc标志位和has_sidetable_rc
                if (!tryRetain && !sideTableLocked) sidetable_lock();
                sideTableLocked = true;
                transcribeToSideTable = true;
                newisa.extra_rc = RC_HALF;
                newisa.has_sidetable_rc = true;
            }
        } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
    
        if (slowpath(transcribeToSideTable)) {
            sidetable_addExtraRC_nolock(RC_HALF);
        }
    
        if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
        return (id)this;
    }
    

    进入到rootRetain会进行如下操作:

      1. 判断是否是isTaggedPointerTaggedPointer是不需要维护引用计数的,直接返回。
      1. 不是TaggedPointer,就获取对象的isa,判断是不是nonpointer isa
      1. 不是nonpointer isa,交给散列表处理,对引用计数进行++操作,然后返回
      1. 判断是不是正在deallocating,是的话直接返回
      1. nonpointer isa,对isa的标志位extra_rc执行++操作
      1. 如果计数超出extra_rc能存储的范围了,就将其中的一半存在extra_rc,并把has_sidetable_rc标志位置为1。然后拷贝另外一半放入散列表进行保存。

    散列表是多张表,由于性能和安全的考虑,是多张而不是一张,但是多张并不是每个对象就一张。散列表的存储引用计数的方式如下,也就是对存储的引用计数进行++操作:

    bool
    objc_object::sidetable_tryRetain()
    {
        SideTable& table = SideTables()[this];
    
        bool result = true;
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it == table.refcnts.end()) {
            table.refcnts[this] = SIDE_TABLE_RC_ONE;
        } else if (it->second & SIDE_TABLE_DEALLOCATING) {
            result = false;
        } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
            it->second += SIDE_TABLE_RC_ONE;
        }
        
        return result;
    }
    
    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;
    }
    

    release

    retain是引用计数+1,而release是引用计数-1,流程是相辅相成的。调用release还是会进入到objc_release方法。

    void 
    objc_release(id obj)
    {
        if (!obj) return;
        if (obj->isTaggedPointer()) return;
        return obj->release();
    }
    
    inline void
    objc_object::release()
    {
        assert(!isTaggedPointer());
    
        if (fastpath(!ISA()->hasCustomRR())) {
            rootRelease();
            return;
        }
    
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
    }
    

    可以看到,调用objc_release也会先判断是否是isTaggedPointer,如果是就直接返回。接着会判断对象没有自定义retain/release方法就会调用rootRelease,否则就通过objc_msgSend发送SEL_release消息。

    bool 
    objc_object::rootRelease()
    {
        return rootRelease(true, false);
    }
    
    objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
    {
        // isTaggedPointer 不做处理 直接返回
        if (isTaggedPointer()) return false;
    
        bool sideTableLocked = false;
    
        isa_t oldisa;
        isa_t newisa;
    
     retry:
        do {
            oldisa = LoadExclusive(&isa.bits);
            newisa = oldisa;
            if (slowpath(!newisa.nonpointer)) {
                // 不是nonpointer_isa 对散列表中的引用计数进行处理
                ClearExclusive(&isa.bits);
                if (sideTableLocked) sidetable_unlock();
                return sidetable_release(performDealloc);
            }
            // nonpointer_isa 对isa的extra_rc 进行--操作
            uintptr_t carry;
            newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  
            if (slowpath(carry)) {
                // 如果不够减,则需要去散列表的引用计数表中借位
                goto underflow;
            }
        } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                                 oldisa.bits, newisa.bits)));
    
        if (slowpath(sideTableLocked)) sidetable_unlock();
        return false;
    
     underflow:
        newisa = oldisa;
        
        // 此时isa的extra_rc已经清零 没有计数了
        // isa的散列表引用标志位有值
        if (slowpath(newisa.has_sidetable_rc)) {
            if (!handleUnderflow) {
                ClearExclusive(&isa.bits);
                // 递归调用
                return rootRelease_underflow(performDealloc);
            }
    
            if (!sideTableLocked) {
                ClearExclusive(&isa.bits);
                sidetable_lock();
                sideTableLocked = true;
                
                // 散列表没加锁,加锁 递归
                goto retry;
            }
    
            // 把散列表里存储的引用计数取出来   
            size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
    
            // 散列表中的引用计数如果大于0
            if (borrowed > 0) {
                // 对散列表的引用计数做--操作 然后存入isa的extra_rc
                newisa.extra_rc = borrowed - 1;  
                bool stored = StoreReleaseExclusive(&isa.bits, 
                                                    oldisa.bits, newisa.bits);
                if (!stored) {
                    // 如果没有成功的存入 isa的extra_rc 那就再存一遍
                    ......
                }
    
                if (!stored) {
                    // 二次存入还是没有成功 就把数据放回到散列表的引用计数表
                    sidetable_addExtraRC_nolock(borrowed);
                    goto retry;
                }
    
                // 从散列表借位--成功
                sidetable_unlock();
                return false;
            }
            else {
                // 散列表的引用计数也是空的
            }
        }
    
        // isa 没有在deallocating中 那抛出异常
        if (slowpath(newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) sidetable_unlock();
            return overrelease_error();
        }
        // 将isa置为deallocating,然后再来递归一遍
        newisa.deallocating = true;
        if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
    
        if (slowpath(sideTableLocked)) sidetable_unlock();
        
        __sync_synchronize();
        if (performDealloc) {
            // 发送一个SEL_dealloc的消息
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
        }
        return true;
    }
    

    梳理一下,进入到rootRelease之后的流程如下:

      1. 判断是否是isTaggedPointerTaggedPointer是不需要维护引用计数的,直接返回。
      1. 不是TaggedPointer,就获取对象的isa,判断是不是nonpointer isa
      1. 不是nonpointer isa,交给散列表处理,对引用计数进行--操作,如果散列表的引用计数清零,就需要对该对象发送SEL_dealloc信息,执行dealloc操作,然后返回。
      1. 如果是nonpointer_isa就对isaextra_rc进行--操作,当extra_rc计数为0,不够减的时候,就需要从散列表的引用计数表借位减。
      1. 判断isahas_sidetable_rc是否有值,有值就进行第6步,没有值就进行第8步
      1. 获取散列表的引用计数,如果计数等于0,就对该对象发送SEL_dealloc信息,执行dealloc操作。
      1. 如果从散列表获取的引用计数大于0,将计数减1,然后存入isaextra_rc,返回。如果没有存入成功,则进行2次递归存储,如果还是没有成功,就将计数存入散列表,继续进行一次递归操作。
      1. 判断isa是否正处于deallocating,如果没有那就抛出异常。
      1. 对该对象发送SEL_dealloc信息,执行dealloc操作。

    散列表的引用计数表release引用计数的操作:

    uintptr_t
    objc_object::sidetable_release(bool performDealloc)
    {
        SideTable& table = SideTables()[this];
    
        bool do_dealloc = false;
    
        table.lock();
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it == table.refcnts.end()) {
            do_dealloc = true;
            table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
        } else if (it->second < SIDE_TABLE_DEALLOCATING) {
            do_dealloc = true;
            it->second |= SIDE_TABLE_DEALLOCATING;
        } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
            it->second -= SIDE_TABLE_RC_ONE;
        }
        table.unlock();
        if (do_dealloc  &&  performDealloc) {
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
        }
        return do_dealloc;
    }
    

    dealloc

    当页面销毁或者对象销毁的时候就会进入dealloc方法进行相关的处理。

    - (void)dealloc {
        _objc_rootDealloc(self);
    }
    
    void
    _objc_rootDealloc(id obj)
    {
        assert(obj);
        obj->rootDealloc();
    }
    
    inline void
    objc_object::rootDealloc()
    {
        // TaggedPointer 不用处理引用计数
        if (isTaggedPointer()) return;  // fixme necessary?
        
        // 是nonpointer
        // 没有弱引用表、没有关联对象、没有c++析构器、没有散列表引用计数
        // 直接释放
        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);
        }
    }
    
    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();
    
            // 存在c++析构函数 调用析构
            if (cxx) object_cxxDestruct(obj);
            // 存在关联对象 删除关联对象
            if (assoc) _object_remove_assocations(obj);
            obj->clearDeallocating();
        }
    
        return obj;
    }
    
    inline void 
    objc_object::clearDeallocating()
    {
        if (slowpath(!isa.nonpointer)) {
            // 非non-pointer isa
            sidetable_clearDeallocating();
        }
        else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
            // non-pointer isa
            clearDeallocating_slow();
        }
    
        assert(!sidetable_present());
    }
    
    void 
    objc_object::sidetable_clearDeallocating()
    {
        SideTable& table = SideTables()[this];
    
        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();
    }
    
    void
    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. 如果是TaggedPointer,则不用处理引用计数,返回
      1. 如果是nonpointer_isa,且没有弱引用表、没有关联对象、没有c++析构器、没有散列表引用计数,那就直接释放,否则进入第3步,调用object_dispose()
      1. 调用objc_destructInstance(obj),然后在调用free(obj)
      1. objc_destructInstance()方法中,判断如果存在cxx析构器则需要调用析构方法,如果存在关联对象需要删除关联对象。
      1. 清除弱引用表中的相关信息,清除散列表中引用计数表的信息。

    弱引用表的释放详见weak原理,此处就不赘述。

    总结

    retain流程

    retain.png

    release流程

    release.png

    dealloc流程

    dealloc.png

    相关文章

      网友评论

        本文标题:iOS-内存管理(二)-引用计数

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