美文网首页
Objective-C 对象的内存管理-引用计数的管理

Objective-C 对象的内存管理-引用计数的管理

作者: _涼城 | 来源:发表于2022-04-01 17:07 被阅读0次

什么是自动引用计数

    Objective-C 提供了两种应用程序堆空间内存管理方法,手动引用计数(MRC)和自动引用计数(ARC),区别在于 ARC 模式下,编译会插入适当的内存管理方法调用,也就是LLVM 编译器来进行内存管理

在 LLVM 编译器中设置 ARC 为有效状态,就无需在使用 retain 和 release。 具体体现在 Xcode->Target->Build Settings-> Objective-C Automatic Reference Counting

    尽管苹果会自动管理对象的引用计数,但是开发中仍然会有问题出现,例如循环引用,如下图:


retaincycles_2x.png

    为了更好的解决这些问题,有必要了解什么是引用计数以及引用计数是怎么实现的。

什么是引用计数

    移动端的内存管理技术,主要有 GC(Garbage Collection,垃圾回收)的标记清除算法和苹果公司使用的引用计数方法。相比较于 GC 标记清除算法,引用计数法可以及时地回收引用计数为 0 的对象,减少查找次数。

    引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。使用引用计数技术可以实现自动资源管理的目的。每一个对象都有一个关联的引用计数 —— 对该对象的活跃引用的数量。

引用计数内存管理的思考方式

  • 自己生成的对象,自己持有
  • 非自己生成的对象,自己也能持有
  • 不再需要自己持有的对象时释放
  • 非自己持有的对象无法释放

Objecive-C 内存管理方法

Objective-C 中,关于引用计数的内存管理协议和方法,是由 Foundation 框架中的 NSObject 抽象基类提供的,如下表所示:

对象操作 Objecive-C 方法
生成对象 alloc、new、copy
持有对象 retain
释放对象 release
销毁对象 dealloc

通过上面的方法,实现了控制每一个对象关联的引用计数 retainCount 的增加或减少,如下图所示:

通过 objc 源码,可以了解 Objective-C 内存管理中使用的 alloc/retainCount/retain/release//dealloc 内部细节,从而理解苹果的引用计数原理。

alloc

    在 objc-787.1 源码及以前可以找到 NSObject类 的 alloc 方法,其核心函数为 _class_createInstanceFromZone ,做了以下几件事,可以发现 alloc 方法并未操作引用计数,通过isa结构分析 已经知道 isa 中存储的 extra_rc 是用于存储对象的引用计数。

  • 计算开辟空间的大小 cls->instanceSize,(16字节对齐)

  • 开辟内存空间 calloc

  • 将指针与类进行绑定 obj->initInstanceIsa

      inline void
    
    objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
    {
        ASSERT(!isTaggedPointer());
    
        if (!nonpointer) {
            isa = isa_t((uintptr_t)cls);
        } else {
            ASSERT(!DisableNonpointerIsa);
            ASSERT(!cls->instancesRequireRawIsa());
    
            isa_t newisa(0);
    
    # 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
            newisa.has_cxx_dtor = hasCxxDtor;
            newisa.shiftcls = (uintptr_t)cls >> 3;
    # endif
    
            // 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;
        }
    }
    
    

但是,从 objc-818.2及以后,可以发现 initIsa 函数中增加了 extra_rc 的赋值为 1 ,意味着初始化时引用计数值为 1,源码如下:

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    //...
    newisa.extra_rc = 1;
}

retainCount

查看 NSObject 类的 retainCount 方法,最终调用核心函数为 rootRetainCount。同样,由于 extra_rc初始值不同,也是区分了 objc-787.1objc-818.2

  • objc-787.1

    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();
    }
    
  • objc 818.2

    inline uintptr_t 
    objc_object::rootRetainCount()
    {
          //...
            uintptr_t rc = bits.extra_rc;
         //...
    }
    
    

根据上面的源码,我们可以发现具体代码流程如下:

  1. rootRetainCount 函数会先判断 nonpointer(也就是 isa 中是否有类信息)

  2. 如果 isa 中有类信息,则先从 isa 中取出 extra_rc 值,之后判断散列表SideTable 中是否存储引用计数并相加。

  3. 如果 isa 中没有存储类信息,则直接调用 sidetable_retainCount 获取引用计数值。

两个 objc 版本不同点在于,objc-787.1 中会对结果进行 +1 操作。

retain

查看 NSObject 类的 retain 方法,最终调用核心函数为 rootRetain


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;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        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
        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);
                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;
        }
    } 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;
}

根据上面的源码,我们可以发现具体代码流程如下:

  1. 如果不是nonpointer isa,则直接对散列表 sidetable 操作引用计数;
  2. 否则,如果当前正在释放,则停止。
  3. 可以操作引用计数,则进行 extra_rc++,对应代码 newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry)RC_ONE1;
  4. 如果 extra_rc++ 上溢出,则 carry 有值,则处理 extra_rc 最大值的一半 RC_HALF 存储至散列表中,extra_rc 中保存另一半;并将 has_sidetable_rc置为 true。由于版本不同架构不同,extra_rc 最大值值可能也会不同,目前,可存储 8 位。

release

查看 NSObject 类的 release 方法,最终调用核心函数为 rootRelease

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.
        }
    }

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

根据上面的源码,我们可以发现具体代码流程如下:

  1. 如果不是nonpointer isa,则直接对散列表 sidetable 操作引用计数;

  2. 进行 extra_rc--,对应代码为 newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);

  3. 如果此时 extra_rc 的下溢出,则走到 underflow,否则结束;

  4. 判断 has_sidetable_rc 散列表中是否存储了一半的引用计数,从散列表中取出存储的一半引用计数,进行 -1 操作,然后存储到 extra_rc 中;

  5. 如果 extra_rc 依旧下溢出,则直接自动触发 dealloc 流程,deallocating 赋值为 true,发送 dealloc 消息

dealloc

查看 NSObject 类的 dealloc 方法,最终调用核心函数为 rootDealloc

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

    if (fastpath(isa.nonpointer                     &&
                 !isa.weakly_referenced             &&
                 !isa.has_assoc                     &&
#if ISA_HAS_CXX_DTOR_BIT
                 !isa.has_cxx_dtor                  &&
#else
                 !isa.getClass(false)->hasCxxDtor() &&
#endif
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

根据上面的源码,我们可以发现具体代码流程如下:

  1. 根据条件判断是否有 isacxx、关联对象、弱引用表、引用计数表,如果没有,则直接 free 释放内存
  2. 如果有,则进入 object_dispose 方法

查看 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.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        //调用C ++析构函数
        if (cxx) object_cxxDestruct(obj);
        //删除关联引用
        if (assoc) _object_remove_assocations(obj);
        //释放
        obj->clearDeallocating();
    }

    return obj;
}
👇
inline void 
objc_object::clearDeallocating()
{
    //判断是否为nonpointer isa
    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());
}
👇
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();
}

根据上面的源码,我们可以发现具体代码流程如下:

  • 销毁实例

    • 调用c++析构函数
    • 删除关联引用
    • 释放散列表
    • 清空弱引用表
  • free释放内存

总结

  1. objc-787.1 以前,alloc 创建对象的引用计数为 0,会在编译时期,程序默认加 1,所以读取 retainCount 时为 1,在 objc-787.1 以后,alloc 创建对象的引用计数为 1;

  2. retain 函数操作 isa->bit->extra_rc++,存储 8 位 ,超过最大值后会存储至散列表中;

  3. release 函数操作 isa->bit->extra_rc--extra_rc 下溢出时,如果 has_sidetable_rctrue,则取出 -1 赋值给 extra_rc;如果 has_sidetable_rcfalse 则释放,调用 dealloc

  4. dealloc 会调用 c++析构函数、删除关联引用、释放散列表、清空弱引用表,最后释放内存

相关文章

  • iOS 面试知识点(三)

    Objective-C的内存管理 引用计数机制 Objective-C 主要采用引用计数机制来管理对象的内存。 运...

  • EffectiveObjective-C2.0 笔记 - 第五部

    5 内存管理 5.1 理解引用计数 1、引用计数 Objective-C 语言使用引用计数来管理内存,每个对象都有...

  • iOS内存管理

    一.内存管理 /引用计数 Objective-C 中的内存管理,也就是引用计数 1.1内存管理的思考方式 自己生成...

  • Objective-C中的内存管理

    本文来自内存管理文档的整理 在Objective-C中内存管理是基于引用计数的,所谓的引用计数就是每个对象都会有一...

  • iOS内存管理

    1. 引用计数 1.1 引用计数原理 Objective-C 使用引用计数管理内存。新创建的对象引用计数至少为1,...

  • Objective-C 对象的内存管理-SideTables

    前言 在Objective-C 对象的内存管理-引用计数的管理[https://www.jianshu.com/p...

  • Effective Objective-C 2.0 总结(五)

    内存管理 第 29 条:理解引用计数 引用计数工作原理 Objective-C 语言使用引用计数来管理内存,每个对...

  • 【iOS小结】内存管理

    MRC下的内存管理 引用计数的思考 Objective-C中的内存管理,也就是引用计数。有关内存管理的方法是包含在...

  • iOS 是如何管理内存的?

    Objective-C内存管理机制 Objective-C中的对象都是基于引用计数来管理生命周期的。简单来说就是,...

  • iOS 内存管理

    内存管理的原理 iOS 内存管理,是基于引用计数来管理内存;当对象引用计数为0时,对象将被销毁,回收内存空间;内存...

网友评论

      本文标题:Objective-C 对象的内存管理-引用计数的管理

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