18 - 内存管理之retain/release/dealloc/retainCount的底层分析

568 阅读9分钟

OC底层原理探索文档汇总

本文主要是在源码层面上分析内存管理的引用计数的变化过程以及。包括alloc/retain/release/dealloc/retainCount的源码分析

主要内容:

1、引用计数的过程

2、弱引用表的结构

3、sideTable散列表的结构

retain的分析

retain

在源码中全局搜索retain()

源码:

// Equivalent to calling [this retain], with shortcuts if there is no override
//相当于调用[this retain],如果没有重写,则使用快捷方式
inline id 
objc_object::retain()
{
    ASSERT(!isTaggedPointer());

    //如果没有重写retain方法,就会执行默认的
    if (fastpath(!ISA()->hasCustomRR())) {
        return rootRetain();
    }

    //如果本类已经重写了,就和普通的方法调用一样直接调用重写的方法即可
    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}

说明:

  • 如果我们重写了retain方法,会进行消息发送来调用
  • 如果没有重写,就直接调用已有的rootRetain函数
  • 因此我们平时要重写retain方法就需要[super retain]

rootRetain

通过retain -> rootRetain() -> rootRetain(bool tryRetain, bool handleOverflow),找到rootRetain函数进行分析

源码:

/*
 进行一次reatain真正进行的操作
 过程:
    1、如果不是nonpointr_isa,就直接操作散列表
    2、如果正在释放,就直接dealloc
    3、引用计数+1
    4、判断如果carry已经满了,就将引用计数的一半放到散列表中,一半放到extra_rc中
 */
ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;//小对象类型直接返回,不进行任何操作

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;//转移到sideTable

    //这里使用isa是因为isa中存储有引用计数
    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        //判断是否为nonpointer isa
        if (slowpath(!newisa.nonpointer)) {
            //如果不是 nonpointer isa,直接操作散列表sidetable
            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
        //是否正在释放,直接dealloc
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        uintptr_t carry;
        //执行引用计数+1操作,即对bits中的extra_rc进行+1操作,通过1ULL<<45(arm64,用于该对象存储引用计数值
        //状态标识carry,用于表示extra_rc是否满了
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        //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.
            //这里只将一半存入到extra_rc中,剩下的一半存储到散列表中
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            
            //给isa的extra_rc设置数值
            newisa.extra_rc = RC_HALF;
            //给isa的has_sidetable_rc设置标识符为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.
        //复制另一个半到散列表中
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}

isa存储情况.png 代码结构:

  1. 判断如果是小对象类型,不进行retain,直接返回
  2. 判断如果不是nonpointer,就直接操作散列表
    • 如果是元类就直接返回,元类无需进行引用计数的计算(前面我们分析过,元类不是nonpointer_isa)
    • 如果不是元类,直接操作散列表sidetable
  3. 判断如果该对象正在dealloc,不作任何操作直接退出
  4. 对isa中的extra_rc进行+1操作,并且通过carry存储是否已满
  5. 如果extra_rc已经达到最大值,数据已满,carry就是YES
  6. 当carry为YES,就将extra_rc中的一半取出来赋值到isa中的extra_rc,另一半拿出来存储到sidetable的计数表中。并且设置isa中的has_sidetable_rc为YES,表示散列表中存储有引用计数。

说明:

  • 不是nonpointer_isa就只有散列表存储引用计数,这也很容易理解,因为非nonpointer的isa只存储有类信息,没有其他信息。无法存储引用计数
  • nonpointer_isa有两个值,一个是extra_rc,一个是has_sidetable_rc,分别用来存储引用计数和是否存储有sidetable。
  • 如果extra_rc没有满,引用计数只存储在extar_rc(arm占有19位)
  • 如果extra_rc已经满了,就需要取出数据的一半存储在sidetable。并且将isa中的has_sidetable_rc设置为YES(has_sidetable_rc占有两位)

retain的过程:

  • 先给isa中的extra_rc引用计数+1
  • 如果extra_rc溢出,就取出一半放到sideTable中的引用计数表中。只留一半在extra_rc中
  • 如果正在dealloc,就直接dealloc不进行任何操作

sidetable的认识:

源码:

// RefcountMap disguises its pointers because we 
// don't want the table to act as a root for `leaks`.
//这个引用计数表是对象作为键,引用计数作为值来存储的
//当引用计数为0时,会自动将其objc删除掉
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;

// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };

//额外的散列表
struct SideTable {
    spinlock_t slock;//自旋锁,对于表的使用需要加解锁
    RefcountMap refcnts;//引用计数表,仅在未开启isa优化 或 在isa优化情况下isa_t的引用计数溢出时才会用到
    weak_table_t weak_table;//弱引用表,弱引用计数存储在这里

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    //可以看到SideTable是不可以析构的,
    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    //锁的操作,SideTable的读取需要进行加解锁
    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

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

说明:

  • 额外信息表SideTable存储有引用计数表和弱引用表
  • 引用计数表仅在未开启isa优化 或 在isa优化情况下isa_t的引用计数溢出时才会用到
  • 这个引用计数表是对象作为键,引用计数作为值来存储的
  • 当一个对象被弱引用时,就需要在弱引用表中进行存储
  • 同时在这里还有一个自旋锁,是通过spinlock_t来实现的
  • 自旋锁简单来说会忙等待,效率高。
  • 加锁是为了线程安全,防止对引用计数的读取出现差错。
  • 同时看到SideTable是不可以删除的,这个是系统自己创建的,我们无法删除

weak_table_t

源码:

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 * 全局弱引用表。
 * 将对象ID存储为键,将weak_entry_t结构体存储为值
 */
struct weak_table_t {
    weak_entry_t *weak_entries;//是一个动态数组,里面存储了很多的弱引用
    size_t    num_entries;//弱引用的数量
    uintptr_t mask;//用来哈希计算,为总数-1
    uintptr_t max_hash_displacement;//可能发生的最大冲突数
};

说明:

  • 可以看到weak_table_t这是一个全局的弱引用表,里面存储有多个弱引用表
  • 在weak_entries中对象ID作为键,weak_entry_t作为值存储。

总结:

  • 弱引用表存在于sideTable表中
  • isa中有weakly_refrenced来判断是否存在弱引用
  • 弱引用表使用对象ID作为键,弱引用动态数组作为值存储

release

通过objc_release() -> release() -> rootRelease() -> rootRelease(bool performDealloc, bool handleUnderflow)查找到是在rootRelease函数中进行了release操作

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)) {//如果不是nonpointer_isa,就直接操作sideTable,进行减一
            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
        //进行引用计数-1操作,即extra_rc-1
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
        //如果此时extra_rc的值为0了,则走到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;

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate
    // 如果isa中的extra_rc减完之后已经为0,就开始从sideTable获取数据,或者如果全部为0,就开始deallocate
    // abandon newisa to undo the decrement
    newisa = oldisa;

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

        // Transfer retain count from side table to inline storage.
        //从sideTable转移引用计数到extra_rc
        //先上锁
        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.
        //即使已经为0了,也需要设置has_sidetable_rc
        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            //extra_rc有数据后,就再-1
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too,计数-1
            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.

    //如果没有就直接dealloc
    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;
}

说明:

  • 过程与retain的刚好相反
  • 先直接将isa中的extra_rc中的数据进行-1
  • 如果已经为0,则从sideTable获取数据
  • 如果sideTable中也没有数据,就进行dealloc
  • 如果有数据,就取出一半的数据放到extra_rc中,再对extra_rc进行-1

release的过程为:

  • 如果isa中的extra_rc引用计数值不为0,就直接减1;
  • 如果值为0,就先从sideTable的引用计数获取值,取出其中的一半存储到extra_rc中,并继续-1.
  • 如果减完之后引用计数为0,就开始dealloc。

dealloc

在源码中通过dealloc -> _objc_rootDealloc -> rootDealloc,在rootDealloc中进行真正的dealloc操作

rootDealloc

源码:

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    //如果是nonpointer、而且有弱引用,关联对象、是否有析构器,是否有存储的引用计数
    /*
     如果是nonpointer_isa,不会进
     如果不是nonpointer_isa,这几个条件有一个还在,就不会进
     */
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    }
    //如果非nonpointer_isa或者是nonpointer_isa,且存在那几个条件就需要销毁一些东西
    else {
        object_dispose((id)this);
    }
}

说明:

  • 如果是小对象类型无需操作
  • 如果是nonpointer_isa,但是不存在弱引用表、关联对象、析构器、额外数据表sidetable,就可以直接删掉
  • 如果是非nonpointer_isa,或者是nonpointer_isa,但是存在弱引用表、关联对象、析构器、额外数据表sidetable,就需要先删除这些东西。

object_dispose

源码:

id 
object_dispose(id obj)
{
    if (!obj) return nil;

    //先破坏对象
    objc_destructInstance(obj);
    //再清除空间
    free(obj);

    return nil;
}

说明:

  • 这里只是调用objc_destructInstance进行销毁对象,之后再清除对象的空间

objc_destructInstance

源码:

/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory. 这里只销毁实例,不在这里清除内存
* Calls C++ destructors.调用C++析构器
* Calls ARC ivar cleanup.调用ARC变量清除
* Removes associative references.移除关联对象
* Returns `obj`. Does nothing if `obj` is nil.将返回obj,如果obj是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.
        //这个顺序很重要。
        /*
         1、先进行C++析构函数调用
         2、移除关联对象
         3、弱引用
         4、sideTable
         */
        /*
         如果是nonapoint_isa,,不移除弱引用
         */
        if (cxx) object_cxxDestruct(obj);//C++析构函数
        if (assoc) _object_remove_assocations(obj);//删除关联对象
        obj->clearDeallocating();
    }

    return obj;
}

clearDeallocating

源码:

/*
 删除has_sidetable_rc和extra_rc
 */
inline void 
objc_object::clearDeallocating()
{
    //如果不是nonpointer,清除has_sidetable_rc和extra_rc
    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());
}

说明:

  • 这里只销毁,不清除内存
  • 一个是注意销毁的内容包括C++析构函数、关联对象、弱引用、sideTable。
  • 第二个是注意销毁的顺序为C++析构函数、关联对象、弱引用、sideTable。

总结:

  • dealloc时需要先清除C++析构函数、关联对象、弱引用、sideTable,通过isa来判断是否需要清除
  • 清除的顺序为C++析构函数、关联对象、弱引用、sideTable

retainCount

源码:

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;//小对象类型不用处理

    sidetable_lock();
    isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
    //如果是nonpointer,取出extra_rc和sidetable中的引用计数加在一起取出来
    if (bits.nonpointer) {
        uintptr_t rc = bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

说明:

  • extra_rc和sidetable中的引用计数加在一起就是这个对象的引用计数值

看一个面试题打印什么?

- (void)test2{
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)[NSObject alloc]));
}

说明:

  • 查看运行结果打印的是1。
  • 此处我们并没有将这个对象附给任何变量,但是就已经引用计数+1了。

初始化isa源码

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    // This write must be performed in a single store in some cases
    // (for example when realizing a class because other threads
    // may simultaneously try to use the class).
    // fixme use atomics here to guarantee single-store and to
    // guarantee memory order w.r.t. the class index table
    // ...but not too atomic because we don't want to hurt instantiation
    isa = newisa;
}

说明:

  • 其他的以前均已经分析过,这里单看newisa.extra_rc = 1;这一条语句,在初始化isa的时候就已经给extra_rc设置为1。
  • 这也就是当alloc一个对象之后引用计数就为1.

总结:

  • retainCount得到的值是isa中的extra_rc中的值加上sideTable中的引用计数的值
  • 当alloc创建出对象时,默认引用计数为1