美文网首页性能优化程序员iOS
alloc、retain、release源码解析

alloc、retain、release源码解析

作者: 聪莞 | 来源:发表于2019-04-11 14:35 被阅读22次

    在MRC时代,系统判定一个对象是否销毁是根据这个对象的引用计数器来判断的。我们要知道一个对象在内存中内存是如何被管理的,还是需要了解一下MRC。设置Build Setting中的Objective-C Automatic Reference Counting为NO,开启手动管理内存。

    引用计数存放位置

    开始本篇源码分析前,我们需要了解引用计数是如何存放的,在上一篇NONPOINTER_ISA和散列表中,我们有说到,NONPOINTER_ISA里面有一位extra_rc用来表示该对象的引用计数,并且当引用计数大于10时,则需要额外再用到sidetable_rc,即散列表里,sideTable里有一个RefcountMap来存放引用计数。

    ReatinCount源码解析

    从一个问题来开始:看如下代码:

    NSObject * obj = [NSObject alloc];
    NSLog(@"obj.retainCount = %ld",obj.retainCount);
    

    obj的引用计数是多少?NSLog会输出几?想回答这个问题,我们可以来分析一下retainCount的源码:

    - (NSUInteger)retainCount {
        return ((id)self)->rootRetainCount();
    }
    inline uintptr_t 
    objc_object::rootRetainCount()
    {
        if (isTaggedPointer()) return (uintptr_t)this;  //taggedPointer没有引用计数这个说法
    
        sidetable_lock();
        isa_t bits = LoadExclusive(&isa.bits);
        ClearExclusive(&isa.bits);
        if (bits.nonpointer) {    //判断是nonPointer类型的isa的话
            uintptr_t rc = 1 + bits.extra_rc;    //先从extra_rc中获取
            if (bits.has_sidetable_rc) {    //如果sideTable也有的话
                rc += sidetable_getExtraRC_nolock();    //加上从sideTable中获取的
            }
            sidetable_unlock();
            return rc;
        }
    
        sidetable_unlock();
        return sidetable_retainCount();
    }
    

    可以看到,在从extra_rc中获取引用计数时,做了个+1的操作,即retainCount返回的值是1 + 计数表总计。
    运行来看一下Log信息:

    2019-04-11 10:13:50.014200+0800 debug-objc[24348:3063630] obj.retainCount = 1
    

    所以,可以得出结论,alloc的对象其真实的引用计数是0,而retainCount为1。

    Alloc

    走进alloc的源码:

    + (id)alloc {
        return _objc_rootAlloc(self);
    }
    id _objc_rootAlloc(Class cls)
    {
        return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
    }
    
    static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
    {
        if (slowpath(checkNil && !cls)) return nil;    //异常判断
    
        if (fastpath(!cls->ISA()->hasCustomAWZ())) {    //是否有自定义的AllocWithZone,有的话就跳过系统的alloc
            if (fastpath(cls->canAllocFast())) {    //是否可以AllocFast
            //** 来看一下canAllocFast
                bool canAllocFast() {
                    assert(!isFuture());
                    return bits.canAllocFast();
                }
                bool canAllocFast() {
                    return false;  //居然直接返回false,what a f**k! 所以我们可以直接去看else里的代码
                }
            */
                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 {
                id obj = class_createInstance(cls, 0);    //调用创建对象
                if (slowpath(!obj)) return callBadAllocHandler(cls);  //一些错误处理
                return obj;
            }
        }
        if (allocWithZone) return [cls allocWithZone:nil];
        return [cls alloc];
    
        //跳入class_createInstance
        id 
        class_createInstance(Class cls, size_t extraBytes)
        {
            return _class_createInstanceFromZone(cls, extraBytes, nil);
        }
    
      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());
    
        //判断有c++的构造和析构
        bool hasCxxCtor = cls->hasCxxCtor();
        bool hasCxxDtor = cls->hasCxxDtor();
        //判断是否可以创建Nonpointer类型
        bool fast = cls->canAllocNonpointer();
    
        //获取需要分配的size
        size_t size = cls->instanceSize(extraBytes);
        if (outAllocatedSize) *outAllocatedSize = size;
    
        id obj;
        if (!zone  &&  fast) {    //zone传进来是nil 而fast一般也都是真 所以几乎都会进这里
            obj = (id)calloc(1, size);      //开辟内存
            if (!obj) return nil;
            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;
    
            obj->initIsa(cls);
        }
    
        if (cxxConstruct && hasCxxCtor) {
            obj = _objc_constructOrFree(obj, cls);
        }
    
        return obj;
    }
     }
    

    为了避免贴的代码太长,这里做一下分隔,接着跳入initInstanceIsa源码:

    inline void  objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
    {
        assert(!cls->instancesRequireRawIsa());
        assert(hasCxxDtor == cls->hasCxxDtor());
    
        initIsa(cls, true, hasCxxDtor);
    }
    inline void  objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
    { 
        assert(!isTaggedPointer());     //taggedPointer判断
        
        if (!nonpointer) {    //不是nonpointer的话,直接将cls赋给isa.cls,这里的isa就只是一个单纯的指针
            isa.cls = cls;
        } else {    //一般进入else
            assert(!DisableNonpointerIsa);
            assert(!cls->instancesRequireRawIsa());
    
            isa_t newisa(0);    //初始化一个所有位为 0 的指针 isa_t
    
    /**
     __ARM_ARCH_7K__表示的应该是Apple Watch 相关的一些 所以iOS中SUPPORT_INDEXED_ISA会是0
    #if __ARM_ARCH_7K__ >= 2  ||  (__arm64__ && !__LP64__)
    #   define SUPPORT_INDEXED_ISA 1
    #else
    #   define SUPPORT_INDEXED_ISA 0
    #endif
    */
    
    #if SUPPORT_INDEXED_ISA  
            assert(cls->classArrayIndex() > 0);
            ISA_MAGIC_VALUE
            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      //iOS中会走到这里
            //isa_t的bits字段初始化为ISA_MAGIC_VALUE,ISA_MAGIC_VALUE是一个16进制的值,将其转换为二进制后,会发现ISA_MAGIC_VALUE是对nonpointer和magic做初始化。(见下一段代码)
            newisa.bits = ISA_MAGIC_VALUE;
    
            然后将是否有 C++ 析构函数标记
            newisa.has_cxx_dtor = hasCxxDtor;
    
            最后将位移(shift)后的 cls 存入 shiftcls(cls右移3位存到shiftcls中,从isa_t的结构体中也可以看到低3位都是用来存储其他信息的(nonpointer、has_assoc、has_cxx_dtor),既然可以右移三位,那就代表类地址的低三位全部都是0,否则就出错了,补0的作用应该是为了字节对齐。)。
            newisa.shiftcls = (uintptr_t)cls >> 3; 
    #endif
            isa = newisa;
        }
    }
    

    我们来看一下ISA_MAGIC_VALUE对isa的初始化做了什么:

    # if __arm64__
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    # elif __x86_64__
    #   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    

    将其转为2进制分别如下:


    image.png image.png

    按照NONPOINTER_ISA和散列表里的每一位的含义去对应上,可以看到其实是对magic 和 nonpointer 进行初始化。

    Retain

    - (id)retain {
        return ((id)self)->rootRetain();
    }
    id objc_object::rootRetain()
    {
        return rootRetain(false, false);
    }
    
    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 (!tryRetain && sideTableLocked) sidetable_unlock();
                if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
                else return sidetable_retain();
            }
    
            if (slowpath(tryRetain && newisa.deallocating)) {
                ClearExclusive(&isa.bits);
                if (!tryRetain && sideTableLocked) sidetable_unlock();
                return nil;
            }
            uintptr_t carry;    //创建一个临时变量,表示是否有溢出
    
            /**
            #   define RC_ONE   (1ULL<<56) 1向左移56位 然后加进bits 其实就是操作bits位域里的extra_rc++
            */
            newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  
    
            if (slowpath(carry)) {    //如果发生了溢出,即 > 10,需要用到sideTable
                if (!handleOverflow) {
                    ClearExclusive(&isa.bits);     //清理旧值
                    return rootRetain_overflow(tryRetain);
                }
    
                if (!tryRetain && !sideTableLocked) sidetable_lock();
                sideTableLocked = true;      //锁住sideTable
                transcribeToSideTable = true;    //是否需要移动到sideTable
                newisa.extra_rc = RC_HALF;  //新的extra_rc设置为RC_HALF,另外的一半放进sideTable
                newisa.has_sidetable_rc = true;//将是否用到了sideTable置为true
            }
        }
    //判断是否处理完成,不完成的话就一直进行bits的处理
     while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
    
    
        if (slowpath(transcribeToSideTable)) {
            // 另外的一半放进sideTable
            sidetable_addExtraRC_nolock(RC_HALF);
        }
    
       
    
        if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
        return (id)this;
    }
    
    //添加到sideTable
    bool 
    objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
    {
        SideTable& table = SideTables()[this];
    
         //从sideTable的refCountMap中获取旧值
        size_t& refcntStorage = table.refcnts[this];   
        size_t oldRefcnt = refcntStorage;
        if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
    
        uintptr_t carry;
    
        /** delta_rc就是传进来的RC_HALF,注意为什么要左移两位,因为refcountMap的前两位不是表示引用计数的,而是分别表示
            是否有弱引用SIDE_TABLE_WEAKLY_REFERENCED,
            是否正在析构SIDE_TABLE_DEALLOCATING
            */
        size_t newRefcnt = 
            addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
        if (carry) {    //如果也溢出了,那么就进行了一些类似于扩容似的操作,这里不做探究。
            refcntStorage =
                SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
            return true;
        }
        else {
            refcntStorage = newRefcnt;
            return false;
        }
    }
    
    

    总结一下,retain其实就是对引用计数进行了+1操作,如果extra_rc未溢出,那么就直接extra_rc++,如果extra_rc溢出了,就取出一半的值放到sideTable里。

    release

    - (oneway void)release {
        ((id)self)->rootRelease();
    }
    ALWAYS_INLINE bool 
    objc_object::rootRelease()
    {
        return rootRelease(true, false);
    }
    
    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 (sideTableLocked) sidetable_unlock();
                return sidetable_release(performDealloc);
            }
    
            uintptr_t carry;
            //对extra_rc做减操作
            newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
            if (slowpath(carry)) {
              //如果减溢出了,说明extra_rc已经减到了0,就走到underflow
                goto underflow;
            }
        } 
    //做while循环,直到release处理完成
    while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                                 oldisa.bits, newisa.bits)));
    
        if (slowpath(sideTableLocked)) sidetable_unlock();
        return false;
    
     underflow:
        newisa = oldisa;
    
        if (slowpath(newisa.has_sidetable_rc)) {    //如果散列表存在,就操作散列表,否则就开始dealloc
            if (!handleUnderflow) {
                ClearExclusive(&isa.bits);
                return rootRelease_underflow(performDealloc);
            }
    
            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;
            } 
            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);
                        }
                    }
                }
    
                if (!stored) {
                    // Inline update failed.
                    // Put the retains back in the side table.
                    sidetable_addExtraRC_nolock(borrowed);
                    goto retry;
                }
                sidetable_unlock();
                return false;
            }
            else {
                // Side table is empty after all. Fall-through to the dealloc path.
            }
        }
    
        // 开始析构
        if (slowpath(newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) sidetable_unlock();
            return overrelease_error();
        }
        newisa.deallocating = true;
        if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
    
        if (slowpath(sideTableLocked)) sidetable_unlock();
    
        __sync_synchronize();
        if (performDealloc) {    //发送dealloc方法,所以引用计数到0的时候会走dealloc方法
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
        }
        return true;
    }
    
    

    总结一下,release的流程跟retain类似,不过一个是加,一个是减。并且release触发了引用计数为0,那么就直接开始触发dealloc消息。

    为什么这里retain和release都是RC_HALF呢,我们知道,retain会先往extra_rc里加,如果溢出再往sideTable里添加,同样,release也是这样,先从extra_rc里减一,然后从sideTable里取值再减一,为了避免多次操作sideTable,因为sideTable有锁,所以取中间值。

    strong

    strong的源码非常简单,会调用objc_retain来处理新值,调用objc_release释放旧值。

    void
    objc_storeStrong(id *location, id obj)
    {
        id prev = *location;
        if (obj == prev) {
            return;
        }
        objc_retain(obj);
        *location = obj;
        objc_release(prev);
    }
    

    property中的unsafe_unretained

    property中设置属性时,会调用set方法,runtime中set方法的底层源码是这样的:

    static ALWAYS_INLINE 
    void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
    {
        if (!obj  ||  !ivar  ||  obj->isTaggedPointer()) return;
    
        ptrdiff_t offset;
        objc_ivar_memory_management_t memoryManagement;
        _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
    
        if (memoryManagement == objc_ivar_memoryUnknown) {
            //如果是UnKnow,那么设置为strong
            if (assumeStrong) memoryManagement = objc_ivar_memoryStrong;
            else memoryManagement = objc_ivar_memoryUnretained;
        }
    
        id *location = (id *)((char *)obj + offset);
    
        switch (memoryManagement) {
        case objc_ivar_memoryWeak:       objc_storeWeak(location, value); break;
        case objc_ivar_memoryStrong:     objc_storeStrong(location, value); break;
        case objc_ivar_memoryUnretained: *location = value; break;
        case objc_ivar_memoryUnknown:    _objc_fatal("impossible");
        }
    }
    

    switch (memoryManagement) 判断中,可以看到,除了unretained,别的都是通过指针传递,而unretained是通过值传递,所以使用unretained会产生内存泄露。

    @property (unsafe_unretained,nonatomic) NSObject * obj; //会产生野指针,发生内存泄漏。

    相关文章

      网友评论

        本文标题:alloc、retain、release源码解析

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