iOS 内存管理2-MRC&ARC,retain、release、dealloc

oc和swift都是使用引用计数进行管理对象。 每个对象的引用计数是如何存储的?

isa是一个64位的联合体位域,根据CPU架构的不同每个成员的长度不同。
isa结构
isa指针中的extra_rc用于存放引用计数,当引用计数达到一定值时会存储到散列表中。
在objc4-781源码中,查找源码
objc_setProperty->reallySetProperty->objc_retain->retain->rootRetain
其中reallySetProperty主要是对新值的retain,旧值的release。

retain

retain主要工作:

    1. 判断是否为taggedPointer类型,是返回自身,
    1. 如果不是taggedPointer类型,操作引用计数
    • 2.1 判断是否为nonpointer类型,不是则直接操作SideTables散列表,返回散列表中的内容
    • 2.2 判断是否正在释放,如果正在释放,则执行dealloc流程
    • 2.3 执行引用计数extra_rc+1操作,并返回引用计数isa->extra_rc的状态标识carry,用于判断extra_rc是否满了
    • 2.4 如果extra_rc中的引用计数满了,此时需要操作散列表,将满状态的引用计数一半存到extra_rc,另一半存在散列表的rc_half中。
      如果都存储在散列表,每次对散列表操作都需要开解锁,操作耗时,消耗性能大,此操作的目的在于提高性能

nonpinter: 表示是否对 isa 指针开启指针优化,

  • 0:纯isa指针,
  • 1:经过优化后的isa指针

理解slowpathfastpath,用于在编译器编译时判断是否需要进行指令优化,如果是fastpath会让cpu提前预读该语句下分支为1的一条指令,从而减少指令跳转带来的开销。
简单理解fastpath执行该代码的概率大,从而让cpu提前做好准备。slowpath执行该代码的概率小。

源码如下

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
  //1.判断是否为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;
        //2.1 不是nonpointer类型,直接操作散列表
        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();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        //2.2 判断是否正在释放
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        //2.3 extra_rc+1操作,carry表示是否慢
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
      //2.4 如果满了,
        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
          // 如果不处理溢出情况,则在这里会递归调用一次,再进来的时候,handleOverflow会被rootRetain_overflow设置为true,从而进入到下面的溢出处理流程
            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;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } 
    // 将oldisa 替换为 newisa,并赋值给isa.bits(更新isa_t), 如果不成功,do while再试一遍
    while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
    //将extra_rc中一半存入散列表
    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;
}

具体流程图如下:


image.png

散列表

oc将isa中满了的引用计数存储在散列表中,散列表有多张,ios有8张,macos有64张,
操作散列表会进行锁操作会有开销,如果所有的数据都在一张表不安全,而每个对象都有一个散列表开销更大,控制在8张。每个散列表中有refcnts引用计数表(哈希表)存储每个对象的引用计数。
散列表的结构如下

struct SideTable {
    spinlock_t slock;            //自旋锁
    RefcountMap refcnts;     //引用计数表
    weak_table_t weak_table;   //弱引用表
    ...
};

使用散列表的原因

  • 数组:特点数据的是读取快,存储不方便
  • 链表:增删方便,查询慢(需要从头节点开始遍历查询)
  • 散列表:本质就是一张哈希表,哈希表集合了数组和链表的长处,增删改查都比较方便

release

根据retain原理,release进行减一操作。

    1. 判断是否为TaggedPointer类型,是就返回false
    1. 如果不是TaggedPointer,操作引用计数
    • 2.1 判断是否nonpointer,如果是纯指针(即值为0),对散列表中的引用计数减一
    • 2.2 如果nonpointer是1,extra_rc--,并返回carry状态
    • 2.3 根据carry判断,extra_rc是否还有引用计数,carry为1表示extra_rc无值,跳转到underflow中处理散列表。
    • 2.4 extra_rc还有引用计数,则返回false
  • 3 underflow中处理散列表
    • 3.1 如果has_sidetable_rc为true,取出散列表中还有一半的引用计数-1,并存储到extra_rc
    • 3.2 如果performDealloc 为true,则执行delloc
ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
  //判断是否为taggedPointer类型
    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
        //引用extra_rc--操作
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--       
        //如果extra_rc不存在值了,跳转underflow中
        if (slowpath(carry)) {
            // don't ClearExclusive()
    
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;
//处理extra_rc中没有引用计数的情况
 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.
        }
    }

    // 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();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}

dealloc

释放对象需要判断isa指针各成员的值
nonpointer: 是否为优化过的指针,为1
weakly_referenced: 弱引用表,为0
has_assoc:是否存在关联对象,为0
has_cxx_dtor:是否有C++析构函数,为0
has_sidetable_rc: 是否关联散列表,为0

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

由于这里使用了fastPath,dealloc过程绝大多数情况都是在上述条件成立的情况下自动释放对象。

查看object_dispose源码

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.
        //判断是否有c++析构函数
        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());
}

CFGetRetainCount

获取对象的引用计数的数量

    NSObject *obj  =[NSObject alloc];
    NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));

输出


image.png

alloc主要做了三件事


image.png
  • 1.计算对象的内存占用空间
    1. 分配内存空间
    1. 初始化isa指针,

alloc并没有对retain加一操作,查看objc4-781retainCount源码
retainCount->_objc_rootRetainCount->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) {
      //此时读取extra_rc 加一后判断散列表中是否有值,然后返回rc,此过程并没有读写操作
        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();
}

此时读取extra_rc加一后判断散列表中是否有值,然后进行操作后返回rc,此过程并没有写操作
而在最新的objc818.2源码中取消掉了加一操作,可以猜测ios14系统还没使用objc818的源码。

总结:

    1. retain:retain并不是简单的对isa->extra_rc加一,如果extra_rc满了会存储一半的值到散列表。
    1. release:isa->extra_rc减一,如果extra_rc值为空再去判断散列表中查找,散列表有值将其存入extra_rc,没有等待释放
    1. dealloc:释放时判断弱引用表,关联对象,散列表,C++析构
    1. retaincount:读取isa->extra_rc后加一,在去查找散列表中是否有值

你可能感兴趣的:(iOS 内存管理2-MRC&ARC,retain、release、dealloc)