iOS自动引用计数

引用计数:顾名思义,就是对象当前被引用的计数retainCount。当retainCount为0时,表面当前对象没有被任何其它对象引用;当retainCount不为0时,则对象任被系统中其它对象所引用,所以此时对象还仍被使用中,内存不能被系统所回收。只用当retainCount为0时,对象才能被系统所回收。

生成对象的引用计数规则:

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

1、自己生成的对象,自己持有。

当我们使用alloc,new,copy,mutableCopy生成对象时,则当前对象自己持有该引用计数。

NSObject* object = [NSObject new];

2、非自己生成的对象,自己也能持有。​

当我们使用非方法一的方式生成的对象时,此时可能通过其它方法创建生成的对象当前对象并不持有,此时若是想对象自己持有对象,则我们可以使用以下两种方式:

// 方法一
NSMutableArray* array = [NSMutableArray array];
[array retain];

//方法二
NSMutableArray* _strong array = [NSMutableArray array];

3、不再需要自己持有的对象时释放对象。

这个点既是当对象不被任何其它对象引用时,引用计数retainCount为0时则对对象进行释放。

4、非自己持有的对象无法释放。

对象若是不持有该对象,则不能对对象执行release操作,否则对象运行时将会报错。

引用计数原理

1、retain操作

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 (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }

        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            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)) {
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    return (id)this;
}

原理分析:

1、当引用计数不溢出时,则将当前的引用计数extra_rc变量值加1;

2、当引用计数溢出时,也即是当extra_rc变量大于255时,此时将RC_HALF的值也即是128存入sideTable表中,当前引用计数extra_rc的值(256-128=128)为128,之后若是有对象进行引用则再从128开始执行加1计算;在这里代码中有个注意点,当把RC_HALF(128)存进去sideTable表时,delta_rc因为左移两位所以值是512,只是因为sideTable表中低两位是用于作为标识位使用,所以是要左移两位为512,其实存放的还是溢出对象一半的值即128!

2、release操作

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;
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits,
                                             oldisa.bits, newisa.bits)));
    return false;
    
underflow:
    newisa = oldisa;
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
        if (borrowed > 0) {
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits,
                                                oldisa.bits, newisa.bits);
            if (!stored) {
                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);
                    }
                }
            }
    }
    newisa.deallocating = true;
    __sync_synchronize();
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return true;
}

原理分析:

1、当对象之前没存在溢出时,则直接将对象引用计数extra_rc值减1;

2、当对象之前存在溢出时,则sideTable表中则存放着对象之前溢出的值,则将RC_HALF传入取出128的值再进行减1操作,而且当在sideTable表中取了128的值后,当然对应sideTable表中的值也要进行减去RC_HALF的值也即是128的操作;再将对象计数值进行保存,若保存过程对象出现问题,则将对象直接销毁;

3、若对象已经处于正在释放时,则直接调用方法进行释放;

3、retainCount操作

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        return rc;
    }
    return sidetable_retainCount();
}

原理分析:

获取对象引用计数retainCount的操作,则是将对象的bits.extra_rc值和在sideTable表中存放的值进行相加得到的,但为什么还要加1呢,是因为bits.extra_rc存放的引用计数减1后的值,也即是当retainCount等于6时,则extra_rc则为5,这就是要进行加1操作的原因。

以上则是对对象引用计数原理的介绍,生成对象遵循引用计数规则对对象进行合理有效地内存管理。

你可能感兴趣的:(OC高级编程,iOS与OS,X多线程和内存管理)