iOS ARC中引用计数的实现

iOS ARC中引用计数的实现
iOS weak 的实现
ARC中的数据结构以及寻址方式

一、 alloc
  1. alloc---->_objc_rootAlloc---->callAlloc
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
/*1*/     id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
/*2*/     return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

// 实际对象创建函数
static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
/*1*/   obj = (id)calloc(1, size);
/*2*/   if (!obj) return nil;
/*3*/   obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}
  1. if (fastpath(!cls->ISA()->hasCustomAWZ())) 判断有没有自定义的alloc方法,默认没有所以if条件为真, canAllocFast方法固定返回false,所以会执行 class_createInstance 方法
  2. class_createInstance---->_class_createInstanceFromZone
  3. _class_createInstanceFromZone中调用calloc分配空间
  4. initInstanceIsa--->initIsa 为对象结构体中的 isa 赋值
  5. alloc结束 总得来说就是 分配空间、为isa赋值。init默认返回自身 没有额外操作
  6. alloc不会使引用计数+1 此时isa中的引用计数为0,retainCount方法在返回值上+1,所以创建完对象引用计数就会变成1。
...
//  isa 赋值
        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;

二、 retain
  1. retain---->rootRetain----->rootRetain(bool tryRetain, bool handleOverflow)
// 由retain调用至此的两个参数 为false   只保留部分关键代码
ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
// TaggedPointer 是苹果针对64架构的优化,不使用引用计数,所以直接返回本身
    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;

        ...

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

        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            // 第一次调用此法时handleOverflow为false,所以溢出时 调用 rootRetain_overflow 方法内部重新调用 rootRetain,此时handleOverflow为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();
/*2*/       sideTableLocked = true;
/*3*/       transcribeToSideTable = true;
/*4*/       newisa.extra_rc = RC_HALF;
/*5*/       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.
/*6*/   sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}
  1. addc(newisa.bits, RC_ONE, 0, &carry) 对 isa中保留的引用计数extra_rc +1
  2. 如果在上一步操作中 引用计数溢出(64位操作系统中,引用计数占8位),则将extra_rc设置为RC_HALF extra_rc 总共为 8 位,RC_HALF = 0b10000000 代表一半的引用计数,设置has_sidetable_rc为true(代表有额外的引用计数保存在SiteTables里面), 然后调用sidetable_addExtraRC_nolock(RC_HALF)方法,在SiteTables中保存一条记录,将多余的一半引用计数保存进去
  3. 总结:此方法分为两种情况
    未溢出:则在extra_rc的基础上进行+1
    溢出:总大小为8位,所以将一半的引用计数RC_HALF,保存到extra_rc中,一半的引用计数保存到SideTables
  4. SideTables保存对象额外的引用计数和弱引用,后面在单独分析

二、 release
  1. release---->rootRelease----->rootRelease(bool performDealloc, bool handleUnderflow)
正常情况的 release:
//  rootRelease(true, false)    performDealloc = true   handleUnderflow = false

bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) {
    isa_t oldisa;
    isa_t newisa;

    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;

        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
    } while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits));

    return false;
}
  1. LoadExclusive 获取 isa
  2. subc 对 extra_rc -1
  3. StoreReleaseExclusive 保存最新的 isa.bits
发生 underflow 的 release
bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) {
    isa_t oldisa;
    isa_t newisa;

    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;

        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
        if (carry) goto underflow;
    } while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits));
    
    ...

 underflow:
    newisa = oldisa;

    if (newisa.has_sidetable_rc) {
        if (!handleUnderflow) {
            return rootRelease_underflow(performDealloc);
        }

        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        if (borrowed > 0) {
            newisa.extra_rc = borrowed - 1;
            bool stored = StoreExclusive(&isa.bits, oldisa.bits, newisa.bits);

            return false;
        } 
    }
}
  1. 在正常 release 的基础下,如果发生underflow,则调用goto underflow
  2. 判断 newisa.has_sidetable_rc 是否保存有引用计数在 SideTables
  3. 从SideTables中取出 RC_HALF个引用计数在 -1 之后赋值给isa.bits
  4. 后续根据stored判断是否赋值成功,如果没有成功在进行补救操作或者回归操作
release 调用dealloc
bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) {
    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);
        if (carry) goto underflow;
    } while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits));

    ...

 underflow:
    newisa = oldisa;

    if (newisa.deallocating) {
        return overrelease_error();
    }
    newisa.deallocating = true;
    StoreExclusive(&isa.bits, oldisa.bits, newisa.bits);

    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return true;
}
  1. 如果没有SideTables存储引用计数,并且发生underflow,则说明对象已经没有引用,需要被释放
  2. 直接调用 objc_msgSend 向当前对象发送 dealloc 消息。
  3. 利用isa中的deallocating标志位,保证dealloc只会被调用一次

二、 retainCount
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();
}
  1. retainCount = 1 + bits.extra_rc + sidetable_getExtraRC_nolock
  2. 引用计数 = 1 + isa中存储的数量 + SideTables中存储的引用计数

二、 Dealloc
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);
    }
}

当引用计数为0时,系统会自动调用dealloc方法来释放资源

  1. 如果对象没有弱引用、没有关联对象、没有c++析构函数、没有额外的引用计数存在在SideTables中,则直接调用free函数释放空间
  2. 否则调用object_dispose方法
static id 
_object_dispose(id anObject) 
{
    if (anObject==nil) return nil;

    objc_destructInstance(anObject);
    
    anObject->initIsa(_objc_getFreedObjectClass ()); 

    free(anObject);
    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.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}
  1. 调用objc_destructInstance, 调用c++析构函数,移除关联对象,移除SideTables表中相关联的所有数据
  2. 将isa存储的类指针设为nil
  3. 调用free函数释放空间
补充

在MRC时代,经常写下面的代码:

- (void)dealloc {
    self.array = nil;
    self.string = nil;
    // ... //
    // 非Objc对象内存的释放,如CFRelease(...)
    // ... //
    [super dealloc];
}

但是ARC中不需要显示释放属性,只需要移除观察者、Timer等,并且不需要调用[super dealloc]
这是因为系统为我们自己实现了这些方法,而这个方法就是.cxx_destruct,这个方法本来是用来调用C++对象析构函数的,而在ARC中,编译器自动为我们生成这个方法用来释放属性。
在下面链接中有.cxx_destruct的具体分析
http://blog.sunnyxx.com/2014/04/02/objc_dig_arc_dealloc/

总得来说,对象从创建到销毁总共经历以下几个阶段。
1️⃣ alloc,分配空间,并且初始化isa,init返回自身,此时retainCount默认为1
2️⃣ retain, 增加对象引用计数,增加isa中的bits.extra_rc,如果溢出,则保存到SideTables中
3️⃣ release,减少对象引用计数,减少isa中的bits.extra_rc,如果溢出,则取出SideTables中的引用计数,如果SideTables中没有存储,则对象释放,调用 dealloc方法
4️⃣ dealloc,释放对象,移除对象存储的相关信息,包括关联对象、引用计数、弱引用表。

你可能感兴趣的:(iOS ARC中引用计数的实现)