iOS ------ alloc retain release

一,alloc

    YSHStudent *baseStu = [YSHStudent alloc];
    YSHStudent *stu1 = [baseStu init];
    YSHStudent *stu2 = [baseStu init];
    YSHStudent *stu3 = [baseStu init];
    YSHStudent *newStu = [YSHStudent alloc];
    
    NSLog(@"%@---------%p---------%p",baseStu,baseStu,&baseStu);
    NSLog(@"%@---------%p---------%p",stu1,stu1,&stu1);
    NSLog(@"%@---------%p---------%p",stu2,stu2,&stu2);
    NSLog(@"%@---------%p---------%p",stu3,stu3,&stu3);
    NSLog(@"%@---------%p---------%p",newStu,newStu,&newStu);
2021-06-06 10:57:53.766217+0800 alloc[27048:1427279] <YSHStudent: 0x600001f68770>---------0x600001f68770---------0x7ffeeeb8a500
2021-06-06 10:57:53.766808+0800 alloc[27048:1427279] <YSHStudent: 0x600001f68770>---------0x600001f68770---------0x7ffeeeb8a4f8
2021-06-06 10:57:53.767075+0800 alloc[27048:1427279] <YSHStudent: 0x600001f68770>---------0x600001f68770---------0x7ffeeeb8a4f0
2021-06-06 10:57:53.767250+0800 alloc[27048:1427279] <YSHStudent: 0x600001f68780>---------0x600001f68780---------0x7ffeeeb8a4e8

baseStu和stu1、stu2、stu3的对象地址是一模一样的,但是指针地址却不同;
newStu和baseStu、stu1、stu2、stu3不仅指针地址不同,而且对象地址也是不同的;

alloc的主要目的是开辟内存,关联isa和cls类,来看一看具体的底层原理

alloc

+ (id)alloc {
    return _objc_rootAlloc(self);
}

_objc_rootAlloc

id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

callAlloc

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__  //是否有可用的编译器优化
//slowpath 大概率为假
//fastpath 大概率为真
//此处去掉slowpath和fastpath对代码逻辑没有丝毫影响,应该只是告诉编译器对代码优化,提升编译效率。
    if (slowpath(checkNil && !cls)) return nil;
    //判断一个类是否有自定义的 +allocWithZone 实现,没有则走到if里面的实现
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif

    // No shortcuts available.
    if (allocWithZone) {
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}

_objc_rootAllocWithZone

id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    //这里没有什么好说的,直接进入第5步
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}

_class_createInstanceFromZone

主要做了3件事

  • cls->instanceSize(extraBytes);-------计算内存空间
  • (id)calloc(1, size);-------开辟内存空间,返回内存地址指针
  • obj->initInstanceIsa(cls, hasCxxDtor);-------关联isa和类
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;
    //计算需要开辟的内存空间大小
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        //申请内存空间
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        //将cls类和isa关联
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}
instanceSize
    inline size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }

        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }
fastInstanceSize
    size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));

        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            //16字节对齐
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }

我们发现计算内存时是按照16字节对齐的,这样做的原因是什么?

  • 我们需要知道CPU在读取数据时,是以字节块为单位进行读取的,如果频繁读取没有对齐的数据,会严重加大CPU的开销,降低效率
  • 为什么16字节对齐而不是8字节对齐,我们都知道在一个对象中,第一个属性isa占8字节,如果只有8字节的话,不预留空间,可能造成这个对象的isa和另一个对象的isa紧挨着,容易造成访问混乱。同时一个对象也不会只有isa一个属性

由此可见:16字节对齐后,可以加快CPU读取速度同时访问也会更加安全

calloc

calloc:申请内存,返回地址指针。通过instanceSize方法计算的内存大小,想内存中申请大小为size的内存,并赋值给obj。所以obj是指向内存地址的指针。

initInstanceIsa
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}
initIsa
// objc-config.h
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa 
// field as an index into a class table.
// Note, keep this in sync with any .s files which also define it.
// Be sure to edit objc-abi.h as well.
#if __ARM_ARCH_7K__ >= 2  ||  (__arm64__ && !__LP64__)
#   define SUPPORT_INDEXED_ISA 1
#else
#   define SUPPORT_INDEXED_ISA 0
#endif

// objc-object.h
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  // 对于 64 位系统,该值为 0
        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 的 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;
    }
}

initInstanceIsa主要是初始化一个isa指针,初始化isa指针上的has_cxx_dtor,indexcls,magic,nonpointer,shiftcls(存储对象的Class、Meta-Class对象的内存地址信息)

注意这里没有初始化isa的extra_rc,也就是没有将引用计数加一。也就是所在allocate是没有将引用计数加1。在ARC下,对象的引用计数有编译器自动完成,在alloc对象并将它赋值给强应用的对象,ARC会自动插入该对象的retain方法,将引用计数加一。

总结alloc主要有3个核心步骤:

  • 计算内存大小instanceSize
  • 申请内存空间calloc
  • 关联isa和cls类initInstanceIsa

在这里插入图片描述

retain

retain的作用是把对象的引用计数加一

ALWAYS_INLINE id 
objc_object::rootRetain()
{
    return rootRetain(false, false);
}

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    // 如果是 tagged pointer,直接返回 this
    if (isTaggedPointer()) return (id)this; 

    bool sideTableLocked = false;
    bool transcribeToSideTable = false; // 是否需要将引用计数存储在 sideTable 中

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        // 获取 isa
        oldisa = LoadExclusive(&isa.bits);  
        newisa = oldisa; 
        // 如果 isa 不是 nonpointer(优化指针)
        if (slowpath(!newisa.nonpointer)) { 
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return (id)this;
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            // tryRetain == false,调用 sidetable_retain
            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; // 用于判断 isa 的 extra_rc 是否溢出,这里指上溢,即存满
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        // 如果 extra_rc 上溢
        if (slowpath(carry)) { 
            // newisa.extra_rc++ overflowed
            // 如果 handleOverflow == false,调用 rootRetain_overflow
            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 中
            // 准备把另一半引用计数存储到 Sidetable 中
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;   // 设置 transcribeToSideTable 为 true
            newisa.extra_rc = RC_HALF;      // 设置 extra_rc 的值为 RC_HALF   # define RC_HALF  (1ULL<<18)
            newisa.has_sidetable_rc = true; // 设置 has_sidetable_rc 为 true
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); // 保存更新后的 isa.bits

    // 如果需要将溢出的引用计数存储到 sidetable 中
    if (slowpath(transcribeToSideTable)) { 
        // Copy the other half of the retain counts to the side table.
        // 将 RC_HALF 个引用计数存储到 Sidetable 中
        sidetable_addExtraRC_nolock(RC_HALF); 
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}
  • 对于NSTaggedPointer类型,直接返回,不参与引用计数计算,因为NSTaggedPointer对象的值直接存储在了指针中,不必在堆上为其分配内存,这点在objc_msgSend也有体现,首先会判断LNilOrTagged。
  • 获取isa指针,判断isa是不是non pointer(未优化的isa,只存储类和元类对象的地址),如果不是就调用sidetable_retain经过两次的哈希查找得到对象的引用计数表,将引用计数+1,如果不是则执行下面的内容。
  • isa中的extra_rc +1,如果溢出,就将extra_rc的RC_HALF转移到sidetable中存储,extra_rc是19位,而RC_HALF宏是(1ULL<<18),实际上相等于进行了 +1 操作。

这里提到了sidetable,前面讲weak时也用到了。

struct SideTable {
	// 保证原子操作的自旋锁
    spinlock_t slock;
    // 引用计数的 hash 表
    RefcountMap refcnts;
    // weak 引用全局 hash 表
    weak_table_t weak_table;
}

这里用到了refcnts,用来存储OC对象的引用计数的 hash表(仅在未开启isa优化或在isa优化情况下isa_t的引用计数溢出时才会用到)。

sidetable_retain

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];          // 获取 SideTable
    
    table.lock();
    size_t& refcntStorage = table.refcnts[this];    // 获取 refcnt
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { // 如果获取到了,且未溢出
        refcntStorage += SIDE_TABLE_RC_ONE;         // 将引用计数加 1
    }
    table.unlock();

    return (id)this;
}

release

当我们不在需要使用或持有对象时,需要调用一下release方法。release方法会讲对象的引用计数-1.

objc_object::rootRelease

ALWAYS_INLINE bool 
objc_object::rootRelease()
{
    return rootRelease(true, false);
}

ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    // 如果是 tagged pointer,直接返回 false
    if (isTaggedPointer()) return false; 

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        // 获取 isa
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        // 如果 isa 不是 nonpointer
        if (slowpath(!newisa.nonpointer)) { 
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return false;
            if (sideTableLocked) sidetable_unlock();
            // 调用 sidetable_release
            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--
        // 如果发现溢出的情况,这里是下溢,指 extra_rc 中的引用计数已经为 0 了
        if (slowpath(carry)) { 
            // don't ClearExclusive()
            // 执行 underflow 处理下溢
            goto underflow; 
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits))); // 保存更新后的 isa.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
    // extra_rc-- 下溢,从 sidetable 借用或者 dealloc 对象
    newisa = oldisa;

    // 如果 isa 的 has_sidetable_rc 字段值为 1
    if (slowpath(newisa.has_sidetable_rc)) { 
        // 如果 handleUnderflow == false,调用 rootRelease_underflow
        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.        
        // 尝试从 sidetable 中删除(借出)一些引用计数,传入 RC_HALF
        // borrowed 为 sidetable 实际删除(借出)的引用计数
        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.
        // 为了避免竞争,has_sidetable_rc 必须保持设置
        // 即使 sidetable 中的引用计数现在是 0

        if (borrowed > 0) { // 如果 borrowed > 0
            // Side table retain count decreased.
            // Try to add them to the inline count.
            // 将它进行 -1,赋值给 extra_rc 
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            // 存储更改后的 isa.bits
            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);
                    }
                }
            }
            // 如果还是存储失败,把引用计数再重新保存到 sidetable 中
            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.
        }
    }

    // 如果引用计数为 0,dealloc 对象
    // Really deallocate.
    // 如果当前 newisa 处于 deallocating 状态,保证对象只会 dealloc 一次
    if (slowpath(newisa.deallocating)) { 
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        // 调用 overrelease_error
        return overrelease_error(); 
        // does not actually return
    }
    // 设置 newisa 为 deallocating 状态
    newisa.deallocating = true; 
    // 如果存储失败,继续重试
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; 

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    // 如果 performDealloc == true,给对象发送一条 dealloc 消息
    if (performDealloc) { 
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}

sidetable_release

// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa 
// -release paths all return zero in eax
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    // 获取 SideTable
    SideTable& table = SideTables()[this]; 

    bool do_dealloc = false; // 标识是否需要执行 dealloc 方法

    table.lock();
    auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
    // 获取 refcnts
    auto &refcnt = it.first->second; 
    if (it.second) {
        do_dealloc = true;
    // 如果对象处于 deallocating 状态
    } 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)) { 
        // 引用计数 -1
        refcnt -= SIDE_TABLE_RC_ONE; 
    }
    table.unlock();
    // 如果符合判断条件,dealloc 对象
    if (do_dealloc  &&  performDealloc) { 
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return do_dealloc;
}

具体流程:

  • isa不是(isa指针上的)nonpointer,就对Sidetable中的引用计数减1,如果引用计数为0,就dealloc对象
  • isa如果是nonpointer,就讲isa中的extra_rc储存的引用计数减1,如果下溢,引用计数已经减为0,判断has_sidetable_rc(isa指针上的)是否为true即是否使用sideTable存粗引用计数。
  • 如果有,就在sidetable申请RC_HALF个引用计数(如果不足有多少就申请多少),将实际申请的引用计数减1后存储在extra_rc.
  • 如果extra_rc中的引用计数为0,且has_sidetable_rcfalsesidetable中的引用计数也为0,那就dealloc对象。
内存管理方法具体实现
alloc经过一系列的函数调用栈,最终通过C语言的calloc来申请内存空间,并初始化对象的isa指针,但并没有设置对象的引用计数值为1
init基类的init方法啥也没有干,只是将alloc的对象返回。我们可以重写init方法来对alloc创建的实例对象做一些初始化的操作
newnew方法很简单,只是嵌套了alloc和init方法
copy,mutableCopy调用了copyWithZone 和mutableWith方法
retaincount如果isa不是nonpointer,引用计数值 = SideTable中的引用计数表中储存的值 + 对象本身的引用计数 1;如果isa是nonpointer,引用计数值 = isa中的extra_rc存储的值 + SideTable中的引用计数表中储存的值+ 对象本身的引用计数 1|
retain如果isa不是nonpointer,就对Sidetable中的引用计数+ 1; 如果isa是nonpointer,就对Sidetable中的引用计数+ 1,如果溢出,就将extra_rc中的RC_HALF(extra_rc满值的一半)个引用计数转移到sidetable中存储
releaseisa不是nonpointer,就对Sidetable中的引用计数减1,如果引用计数为0,就dealloc对象;isa如果是nonpointer,就讲isa中的extra_rc储存的引用计数减1,如果下溢,引用计数已经减为0,判断has_sidetable_rc(isa指针上的)是否使用sideTable存储引用计数。如果有,就在sidetable申请RC_HALF个引用计数(如果不足有多少就申请多少),将实际申请的引用计数 - 1后存储在extra_rc中。如果extra_rc中引用计数为 0 且has_sidetable_rc为false或者Sidetable中的引用计数也为 0 了,那就dealloc对象
dealloc判断销毁对象前有没有需要处理的东西(如弱引用,关联对象,C++析构函数,sidetable的引用计数表;如果没有就直接调用free函数销毁对象;如果有就先调用object_dispose做一些释放对象前处理(置弱引用指针为nil,移除关联对象,object_cxxDestruct,在sideTable的引用计数表中擦出引用计数等)最后再用free函数销毁对象
weak指针置为nil的过程当一个对象被销毁时,在dealloc方法内部经过一系列的函数调用,通过两次哈希查找,第一次根据对象的地址找到它所在的sidetable,第二次根据对象的地址在sidetable的weak_table找到它的弱引用表。便利弱引用地址,将指针对象地址的weak变量全部置为nil
添加weak经过一系列的函数调用栈。最终在weak_register_no_locck()函数当中,通过哈希查找当前对象的弱引用表,并添加弱引用变量;如果不存在,就创建一个弱引用表,在添加进去
  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值