iOS内存管理一(retainCount,retain,release)

先看下内存布局
image.png

iOS的内存管理方案主要有三种

Tagged Pointer技术
nonpointer isa
散列表(引用计数表,弱引用表)

Tagged Pointer技术
> 从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销
iOS系统是判断指针最高有效位是不是1来判断是不是Tagged Pointer存储的
nonpointer isa

isa占用64位,实际上存储Class内存地址32位就够用,为了提高使用率,会存储一些其他信息,包括小型的引用计数

散列表(引用计数表,弱引用表)

isa中has_sidetable_rc如果为1的话,引用计数就存储在SideTables表中
SideTables其实是很多张哈希表,假如是一张表的话,引用计数的存取都会加锁解锁,这样导致效率很低,苹果为此引入分离锁的方案,把sideTable拆分成若干个表,这样多个对象可以同时访问不同的表,提高了访问效率

#SideTable的结构,只贴出有用的
struct SideTable {
    spinlock_t slock;//自旋锁
    RefcountMap refcnts;//引用计数表
    weak_table_t weak_table;//弱引用表
};
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) {//非指针类型
        //存储在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();
    //指针类型的返回这个
    return sidetable_retainCount();
}

看一下sidetable_getExtraRC_nolock这个内部实现

size_t 
objc_object::sidetable_getExtraRC_nolock()
{
    assert(isa.nonpointer);
    //从SideTables中取出SideTable
    SideTable& table = SideTables()[this];
    //通过this这个key查找这个遍历器
    RefcountMap::iterator it = table.refcnts.find(this);
    //找不到就返回0
    if (it == table.refcnts.end()) return 0;
    //找到it->second向右偏移2位得到的值返回
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}

从上面的底层代码分析得出,采用nonpointer存储的引用计数的值实际上是存在isa中的extra_rc+1+SideTable中取出的值

#指针类型的直接sideTable存储引用计数
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];
    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;
}
看下retain的底层实现
- (id)retain {
    return ((id)self)->rootRetain();
}
objc_object::rootRetain()
{
    return rootRetain(false, false);
}
ALWAYS_INLINE 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;
        //读取操作原子化,根据 CPU 不同实现不同,比如在 x86-64 上就是单纯的直接返回值,而在 arm64 上则使用了 ldxr 指令,获取isa.bits 的值
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        //isa是指针类型
        if (slowpath(!newisa.nonpointer)) {
            //asm("clrex" : "=m" (*dst));
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
           //前面的都不会走,直接走这里
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        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);
                //又调用一下自己的放rootRetain(tryRetain, true);,把处理过溢出标记为true
                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;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
          #留下一半的引用计数,然后把另一半放sideTable里面去
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
    //则将一半的引用计数加sideTable refcnts里面
    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        sidetable_addExtraRC_nolock(RC_HALF);
    }
    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}
#把引用计数存储在sideTable的RefcountMap里面,这里才是我们关注的重点
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;
}
#while循环里面做的事情,不懂汇编,看着像把newisa.bit的值跟isa.bit的值做了对比修改
static ALWAYS_INLINE
bool 
StoreExclusive(uintptr_t *dst, uintptr_t oldvalue __unused, uintptr_t value)
{
    uint32_t result;
    asm("stxr %w0, %x2, [%x3]" 
        : "=r" (result), "=m" (*dst) 
        : "r" (value), "r" (dst));
    return !result;
}

总结一下上面的代码
指针类型的isa,引用计数直接存储在sideTable中的refcntStorage += SIDE_TABLE_RC_ONE
非指针类型的isa是先把引用计数存储在isa的extra_rc里面的,这里还会加之前还会先判断一下是否溢出,如果extra_rc ++要溢出的话就拿出extra_rc一半的引用计数存在sideTable的refcnts中,并且把extra_rc的值改成一半,这样下次引用计数仍旧可以存在extra_rc里面。

看下release的底层实现
- (oneway void)release {
    ((id)self)->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;
        //指针类型sidetable_release
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            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);
            //再执行rootRelease一次,处理下溢出
            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;
        }
//下面是从 sideTable 借 RC_HALF 的引用计数放到 extra_rc 上, 借不到的情况,对象需要被销毁了
        // 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.
        }
    }
    // Really deallocate.
    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
    if (slowpath(sideTableLocked)) sidetable_unlock();
    __sync_synchronize();
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return true;
}

//指针类型的sidetable_release

uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    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) {
        // 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)) {
        it->second -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}

从sideTable的refcnts借RC_HALF过程

size_t 
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
{
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this];

    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()  ||  it->second == 0) {
        // Side table retain count is zero. Can't borrow.
        return 0;
    }
    size_t oldRefcnt = it->second;

    // isa-side bits should not be set here
    assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
    assert(oldRefcnt > newRefcnt);  // shouldn't underflow
    it->second = newRefcnt;
    return delta_rc;
}

总结一下release操作
指针类型的isa还是根据this取出sideTable的refcnts,进行一堆判断,可以销毁的就标记为do_dealloc,最后判断do_dealloc和performDealloc同时为true,然后发送销毁消息
非指针类型isa,先让extra_rc--,如果--后下溢出,就去sideTable中取出RC_HALF,能借到就把借到的值赋值给extra_rc,然后-1保存,借不到的话就发送销毁消息了。

你可能感兴趣的:(iOS内存管理一(retainCount,retain,release))