iOS内存管理07 -- retain, release, dealloc与retainCount的源码分析

  • 本文主要分析几个与对象内存管理相关的几个函数的底层源码实现,包括retainreleasedeallocretainCount

obj->retain()源码

  • 源码实现如下:
inline id objc_object::retain(){
    ASSERT(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        return rootRetain();
    }
    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
ALWAYS_INLINE id 
objc_object::rootRetain(){
    return rootRetain(false, false);
}
  • 内部调用rootRetain,实现如下:
ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow){
    //若是TaggedPointer小对象,直接返回
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    //是否需要将对象的引用计数存储在散列表中
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        //如果isa没有使用优化,直接操作散列表,引用计数+1
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return (id)this;
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        
        //deallocating 是优化的isa指针的属性二进制位
        //如果对象正在释放,则执行dealloc流程,释放弱引用表和引用计数表
        //最后free释放对象内存
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        
        //carry 用来标识经过优化的isa指针的extra_rc(引用计数)是否已满
        uintptr_t carry;
        //经过优化的isa指针 extra_rc++ 即引用计数+1
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        //若isa指针的extra_rc(引用计数)已满
        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            //如果extra_rc满了,则直接将满状态的一半拿出来存到extra_rc
            newisa.extra_rc = RC_HALF;
            //给一个标识符为YES,表示需要存储到散列表
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        //将另一半存在散列表的rc_half中,即满状态下是8位,一半就是1左移7位,即除以2
        //这么操作的目的在于提高性能:
        //因为如果都存在散列表中,当需要release-1时,需要去访问散列表,每次都需要开解锁,比较消耗性能。
        //extra_rc存储一半的话,可以直接操作extra_rc即可,不需要操作散列表。性能会提高很多
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}
  • 具体逻辑步骤如下:
    • 第1步:若对象为TaggedPointer小对象,无需进行内存管理,直接返回;
    • 第2步:若对象的isa没有经过优化,即!newisa.nonpointer成立,由于tryRetain=false,直接进入sidetable_retain方法,此方法本质是直接操作散列表,最后让目标对象的引用计数+1
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;
}
  • SideTable& table = SideTables()[this]根据目标对象本身,在散列表集合SideTables中获取到对应的散列表,由于散列表操作是线程安全的,所以需要进行解锁即table.lock(),散列表集合的结构体如下所示:
template
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        T value alignas(CacheLineSize);
    };

    PaddedT array[StripeCount];
    .....
}
  • SideTables顾名思义是一个散列表的集合,其类型为StripedMap
  • 可以看出在iOS或者iOS模拟器架构下,内存中最多只会存在8张散列表;
  • 散列表的结构体如下所示:
struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;

    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(); }
    ......
}
  • 看到散列表的三个成员:slock是线程锁,refcnts是引用计数表,weak_table是弱引用表;
  • size_t& refcntStorage = table.refcnts[this]:根据对象获取散列表的引用计数表中的引用计数refcntStorage,然后执行+1操作;执行完成时,再上锁;
  • 第3步:判断对象是否正在释放,若正在正在释放,则执行dealloc流程,释放弱引用表和引用计数表;
  • 第4步:若对象的isa经过了优化,则执行newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry),即isa的位域extra_rc+1,且通过变量carry来判断位域extra_rc是否已满,如果位域extra_rc已满则执行newisa.extra_rc = RC_HALF,即将extra_rc满状态的一半拿出来存到extra_rc位域中,然后将另一半存储到散列表中,执行sidetable_addExtraRC_nolock(RC_HALF)函数
未命名.png

obj->release()源码

  • 源码实现如下:
__attribute__((aligned(16), flatten, noinline))
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, @selector(release));
}
  • 内部调用rootRelease()函数:
ALWAYS_INLINE bool objc_object::rootRelease(){
    return rootRelease(true, false);
}
ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow){

    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)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return false;
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);//extra_rc--
        if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate
    // abandon newisa to undo the decrement
    newisa = oldisa;

    //判断对象在散列表中是否存在引用计数
    if (slowpath(newisa.has_sidetable_rc)) {
        if (!handleUnderflow) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.
        if (!sideTableLocked) {
            ClearExclusive(&isa.bits);
            sidetable_lock();
            sideTableLocked = true;
            // Need to start over to avoid a race against 
            // the nonpointer -> raw pointer transition.
            goto retry;
        }

        // Try to remove some retain counts from the side table.
        //获取散列表中对象的引用计数的一半
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        // To avoid races, has_sidetable_rc must remain set 
        // even if the side table count is now zero.
        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            newisa.extra_rc = borrowed - 1;  //redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if (!stored) {
                // Inline update failed. 
                // Try it again right now. This prevents livelock on LL/SC 
                // architectures where the side table access itself may have 
                // dropped the reservation.
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    if (!overflow) {
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            // This decrement cannot be the deallocating decrement - the side 
            // table lock and has_sidetable_rc bit ensure that if everyone 
            // else tried to -release while we worked, the last one would block.
            sidetable_unlock();
            return false;
        }else{
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }

    //来到这里说明 isa的extra_rc引用计数为0且散列表中也为0,即引用计数为0,进入dealloc流程
    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
    }
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    //执行对象的dealloc方法
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}
  • 第1步:若对象为TaggedPointer小对象,不需要做内存管理操作,直接返回;
  • 第2步:若对象的isa没有经过优化,即!newisa.nonpointer成立,直接进入sidetable_release方法,此方法本质是直接操作散列表,最后让目标对象的引用计数-1;实现如下:
uintptr_tobjc_object::sidetable_release(bool performDealloc){
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    table.lock();
    auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
    auto &refcnt = it.first->second;
    if (it.second) {
        do_dealloc = true;
    } else if (refcnt < SIDE_TABLE_DEALLOCATING) {
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        do_dealloc = true;
        refcnt |= SIDE_TABLE_DEALLOCATING;
    } else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
        refcnt -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return do_dealloc;
}
  • 访问对象所在的散列表,再获取对象的引用计数表,最后获取到对象的引用计数refcnt,执行refcnt -= SIDE_TABLE_RC_ONE,即引用计数-1;
  • 在引用计数-1之后,会检测是否满足dealloc条件(引用计数为0),若满足条件,对象执行dealloc流程;
  • 第3步:若对象的isa经过优化,则执行newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry),即对象的isa位域extra_rc-1;且通过变量carry标识对象的isa的extra_rc是否为0, 如果对象的isa的extra_rc=0,则去访问散列表,判断对象在散列表中是否存在引用计数;
  • 若对象在散列表中没有引用计数,表明对象的isa的extra_rc=0且sideTable的引用计数为0,综合之下对象的引用计数为0,则对象执行dealloc流程;
  • 若对象在散列表中有引用计数,执行size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF),即将散列表中一半引用计数borrowed取出来,然后borrowed-1再赋值给对象的isa的位域extra_rc,即newisa.extra_rc = borrowed - 1
object_release.png

dealloc源码

  • 源码实现如下:
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);
    }
}
  • 当对象的isa经过优化,且没有弱引用表,关联其他对象,引用计数表的情况下,直接释放内存即可,执行free(this),否则执行object_dispose((id)this)
id object_dispose(id obj){
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}
  • 内部执行objc_destructInstance(obj)函数,最后再释放对象的内存;
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;
}
  • if (cxx) object_cxxDestruct(obj):调用C++的析构函数,销毁对象;
  • if (assoc) _object_remove_assocations(obj):移除对象的关联对象;
  • 最后执行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());
}
  • 当对象的isa没有经过优化,直接释放散列表,执行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();
}
  • 当对象的isa经过优化,清空弱引用表和引用计数表,执行clearDeallocating_slow();
NEVER_INLINE 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();
}
  • weak_clear_no_lock:清空弱引用表,实现如下:
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;
    
    //referrers哈希表
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } else {//inline_referrers数组
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    //清空目标对象的所有weak指针
    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_t从weak_table_t中移除
    weak_entry_remove(weak_table, entry);
}
dealloc_02.png

retainCount源码分析

  • 源码如下所示:
- (NSUInteger)retainCount {
    return _objc_rootRetainCount(self);
}
  • 内部调用_objc_rootRetainCount函数:
uintptr_t_objc_rootRetainCount(id obj){
    ASSERT(obj);
    return obj->rootRetainCount();
}
inline uintptr_t objc_object::rootRetainCount(){
    if (isTaggedPointer()) return (uintptr_t)this;
    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    //isa经过优化
    if (bits.nonpointer) {
        //获取isa位域的引用计数:extra_rc+1
        uintptr_t rc = 1 + bits.extra_rc;
        //获取散列表中的引用计数
        if (bits.has_sidetable_rc) {
            //两者相加
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }
    sidetable_unlock();
    //isa未经过优化,直接获取散列表中的引用计数
    return sidetable_retainCount();
}
  • 当对象的isa经过优化,首先获取isa位域extra_rc中的引用计数,默认会+1,uintptr_t rc = 1 + bits.extra_rc,然后获取散列表的引用计数表中的引用计数,两者相加得到对象的最终的引用计数,rc += sidetable_getExtraRC_nolock()
  • 当对象的isa没有经过优化,直接获取散列表的引用计数表中的引用计数,返回;
  • 当我们alloc一个对象时,然后调用retainCount函数,得到对象的引用计数为1,是因为在底层rootRetainCount方法中,引用计数默认+1了,这里只有对引用计数的读取操作,是没有写入操作的,简单来说就是:为了防止alloc创建的对象被释放(引用计数为0会被释放),所以在编译阶段,程序底层默认进行了+1操作,实际上在extra_rc中的引用计数仍然为0;
retainCount.png

你可能感兴趣的:(iOS内存管理07 -- retain, release, dealloc与retainCount的源码分析)