[OC学习笔记]ARC与引用计数

Posted Billy Miracle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[OC学习笔记]ARC与引用计数相关的知识,希望对你有一定的参考价值。

通过底层汇编看看ARC是怎么工作的

首先自定义一个类Test
该类有两个类方法,区别在于一个以new开头,调用者持有返回的对象,而create开头的则不持有。
这个也是对应了ARC的方法命名规则:
将内存管理语义在方法名中表示出来早已成为 Objective-C的惯例, 而ARC则将之确立为硬性规定。若方法名以下列词语开头, 则其返回的对象归调用者所有: allocnewcopymutableCopy。若调用上述开头的方法就要负责释放返回的对象。也就是说, 这些对象在MRC中需要你手动的进行释放。若方法名不以上述四个词语开头, 返回的对象就不需要你手动去释放, 因为在方法内部将会自动执行一次autorelease方法。

@interface Test : NSObject

+ (instancetype)createTest;
+ (instancetype)newTest;

@end
@implementation Test

+ (instancetype)createTest 
    return [[self alloc] init];


+ (instancetype)newTest 
    return [[self alloc] init];


@end

Test1

[Test newTest];


可以看出ARC为我们自动添加了release操作(objc_release),ARC优化后相当于:

id temp = [Test newTest];
objc_release(temp);

Test2

id temp = [Test newTest];


可以看到这次出现了一个新的底层函数objc_storeStrong,相当于ARC在底层添加了objc_storeStrong(&temp, temp);,看看objc库中其具体实现:

void
objc_storeStrong(id *location, id obj)

    id prev = *location;
    if (obj == prev) 
        return;
    
    objc_retain(obj);
    *location = obj;
    objc_release(prev);

Test3

self.test = [Test newTest];


这里发了两次消息,新增的消息是发送setTest:的,调试可以看到,ARC在setTest:的加入了objc_storeStrong

相当于:


	id temp = [Test newTest];
    [self setTest:temp];
    objc_release(temp);

- (void)setTest:(Test *test) 
    objc_storeStrong(&_test, test);

Test4

[Test createTest];


相当于:

// Test
+ (instancetype)createTest 
    id temp = [self new];  
    return objc_autoreleaseReturnValue(temp); 
 
// VC 
objc_unsafeClaimAutoreleasedReturnValue([Test createTest]); 

这里可以看出不仅仅多了一个objc_unsafeClaimAutoreleasedReturnValue,并且在create中多了objc_autoreleaseReturnValue,这是因为ARC规则,无需手动释放的内部自动autorelease

enum ReturnDisposition : bool 
    ReturnAtPlus0 = false, ReturnAtPlus1 = true
;

id 
objc_autoreleaseReturnValue(id obj)

    if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;

    return objc_autorelease(obj);

id
objc_unsafeClaimAutoreleasedReturnValue(id obj)

    if (acceptOptimizedReturn() == ReturnAtPlus0) return obj;

    return objc_releaseAndReturn(obj);

  • objc_autoreleaseReturnValue这个函数的作用相当于代替我们手动调用 autorelease,创建了一个autorelease对象。编译器会检测之后的代码,根据返回的对象是否执行 retain操作,来设置全局数据结构中的一个标志位, 来决定是否会执行 autorelease操作。该标记有两个状态,ReturnAtPlus0代表执行 autorelease,以及ReturnAtPlus1代表不执行 autorelease
  • objc_unsafeClaimAutoreleasedReturnValue 这个函数的作用是对autorelease对象不做处理仅仅返回,对非autorelease对象调用objc_release函数并返回。所以本情景中它创建时执行了 autorelease操作了,就不会对其进行 release操作了。只是返回了对象,在合适的实际autoreleasepool会对其进行释放的。

Test5

id temp2 = [Test createTest];


id
objc_retainAutoreleasedReturnValue(id obj)

    if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;

    return objc_retain(obj);

objc_retainAutoreleasedReturnValue这个函数将替代 MRC中的 retain方法, 此函数也会检测刚才提到的那个标志位,如果为ReturnAtPlus0怎执行该对象的 retain操作,否则直接返回对象本身。
在这个例子中, 由于代码中没有对对象进行保留,所以创建时objc_autoreleaseReturnValue函数设置的标志位状态是应该是ReturnAtPlus0。所以,该函数在此处是会进行 retain操作的。

Test6

self.test = [Test createTest];


与前面的类似。

ARC对于修饰符的优化

__strong

与Test2相同

__weak

__weak id temp3 = [Test newTest];


相当于:

id temp = [Test newTest]; 
objc_initWeak(&temp3, temp);
objc_release(temp);
objc_destroyWeak(&temp3);

这个过程就是weak指针的一个周期,从创建到销毁。这上面两个新的runtime函数,objc_initWeakobjc_destroyWeak。这两个函数就是负责创建__weak指针和销毁__weak指针的。其实, 这两个函数内部都引用另一个runtime函数,storeWeak,它是和storeStrong对应的一个函数。

id
objc_initWeak(id *location, id newObj)

    if (!newObj) 
        *location = nil;
        return nil;
    

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);

void
objc_destroyWeak(id *location)

    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);

__unsafe_unretained


仅仅添加了objc_release__unsafe_unretained类型,不具有所有权,所以只是简单的指针赋值, 没有runtime的函数使用。当临时变量temp销毁后, 指针仍然是指向那块内存, 所以是不安全的。正如其名,unretainedunsafe

__autoreleasing


使用__autorelease修饰后, 就相当于为其添加一个autorelease,当autoreleasepool销毁的时候,将其释放掉。

objc_retain的内部逻辑

id 
objc_retain(id obj)

    if (_objc_isTaggedPointerOrNil(obj)) return obj;
    return obj->retain();

static inline bool
_objc_isTaggedPointerOrNil(const void * _Nullable ptr)

    // this function is here so that clang can turn this into
    // a comparison with NULL when this is appropriate
    // it turns out it's not able to in many cases without this
    return !ptr || ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;

如果是TaggedPointer对象或者nil直接返回。接着向下看:

inline id 
objc_object::retain()

    ASSERT(!isTaggedPointer());

    return rootRetain(false, RRVariant::FastOrMsgSend);

非TaggedPointer对象,继续操作:

ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)

    // 如果是 Tagged Pointer 则直接返回 this (Tagged Pointer 不参与引用计数管理,它的内存在栈区,由系统处理)
    if (slowpath(isTaggedPointer())) return (id)this;
    // 临时变量,标记 SideTable 是否加锁
    bool sideTableLocked = false;
    // 临时变量,标记是否需要把引用计数迁移到 SideTable 中
    bool transcribeToSideTable = false;

    // 记录 objc_object 之前的 isa
    isa_t oldisa;
    // 记录 objc_object 修改后的 isa
    isa_t newisa;
    
    // 似乎是原子性操作,读取 &isa.bits。(&为取地址)
    oldisa = LoadExclusive(&isa.bits);

    if (variant == RRVariant::FastOrMsgSend) 
        // These checks are only meaningful for objc_retain()
        // They are here so that we avoid a re-load of the isa.
        // 这些检查仅对objc_retain()有意义
        // 它们在这里,以便我们避免重新加载isa。
        if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) 
            ClearExclusive(&isa.bits);
            if (oldisa.getDecodedClass(false)->canCallSwiftRR()) 
                return swiftRetain.load(memory_order_relaxed)((id)this);
            
            return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
        
    

    if (slowpath(!oldisa.nonpointer)) 
        // a Class is a Class forever, so we can perform this check once
        // outside of the CAS loop
        if (oldisa.getDecodedClass(false)->isMetaClass()) 
            ClearExclusive(&isa.bits);
            return (id)this;
        
    
    
    // 循环结束的条件是 slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))
    // StoreExclusive 函数,如果 &isa.bits 与 oldisa.bits 的内存内容相同,则返回 true,并把 newisa.bits 复制到 &isa.bits,
    // 否则返回 false,并把 &isa.bits 的内容加载到 oldisa.bits 中。
    // 即 do-while 的循环条件是指,&isa.bits 与 oldisa.bits 内容不同,如果它们内容不同,则一直进行循环,
    // 循环的最终目的就是把 newisa.bits 复制到 &isa.bits 中。
    // return __c11_atomic_compare_exchange_weak((_Atomic(uintptr_t) *)dst,
    //                                          &oldvalue, value, __ATOMIC_RELAXED, __ATOMIC_RELAXED)
    
    // _Bool atomic_compare_exchange_weak( volatile A *obj, C* expected, C desired );
    // 定义于头文件 <stdatomic.h>
    // 原子地比较 obj 所指向对象的内存的内容与 expected 所指向的内存的内容,若它们相等,则以 desired 替换前者(进行读修改写操作)。
    // 否则,将 obj 所指向的实际内存内容加载到 *expected (进行加载操作)。
    
    do 
        // 默认不需要转移引用计数到 SideTable
        transcribeToSideTable = false;
        
        // 赋值给 newisa(第一次进来时 &isa.bits, oldisa.bits, newisa.bits 三者是完全相同的)
        newisa = oldisa;
        
        // 如果 newisa 不是优化的 isa (元类的 isa 是原始的 isa (Class cls))
        if (slowpath(!newisa.nonpointer)) 
            
            // 在 mac、arm64e 下不执行任何操作,只在 arm64 下执行 __builtin_arm_clrex();
            // 在 arm64 平台下,清除对 &isa.bits 的独占访问标记。
            ClearExclusive(&isa.bits);
            
            // 如果需要 tryRetain 则调用 sidetable_tryRetain 函数,并根据结果返回 this 或者 nil。
            // 执行此行之前是不需要在当前函数对 SideTable 加锁的
            // sidetable_tryRetain 返回 false 表示对象已被标记为正在释放,
            // 所以此时再执行 retain 操作是没有意义的,所以返回 nil。
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            // 如果不需要 tryRetain 则调用 sidetable_retain()
            else return sidetable_retain(sideTableLocked);
        
        // don't check newisa.fast_rr; we already called any RR overrides
        // 不要检查 newisa.fast_rr; 我们已经调用所有 RR 的重载。
        // 如果 tryRetain 为真并且 objc_object 被标记为正在释放 (newisa.deallocating),则返回 nil
        if (slowpath(newisa.isDeallocating())) 
            ClearExclusive(&isa.bits);
            // SideTable 处于加锁状态
            if (sideTableLocked) 
                ASSERT(variant == RRVariant::Full);
                // 进行解锁
                sidetable_unlock();
            
            // 需要 tryRetain
            if (slowpath(tryRetain)) 
                return nil;
             else 
                return (id)this;
            
        
        // 下面就是 isa 为 nonpointer,并且没有被标记为正在释放的对象
        uintptr_t carry;
        // bits extra_rc 自增
        
        // x86_64 平台下:
        // # define RC_ONE (1ULL<<56)
        // uintptr_t extra_rc : 8
        // extra_rc 内容位于 56~64 位
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
        // 如果 carry 为 true,表示要处理引用计数溢出的情况
        if (slowpath(carry)) 
            // newisa.extra_rc++ overflowed
            // 如果 variant 不为 Full,
            // 则调用 rootRetain_overflow(tryRetain) 它的作用就是把 variant 传为 Full
            // 再次调用 rootRetain 函数,目的就是 extra_rc 发生溢出时,我们一定要处理
            if (variant != RRVariant::Full) 
                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.
            // 将 retain count 的一半留在 inline,并准备将另一半复制到 SideTable.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            // 整个函数只有这里把 sideTableLocked 置为 true
            sideTableLocked = true;
            // 标记需要把引用计数转移到 SideTable 中
            transcribeToSideTable = true;
            // x86_64 平台下:
            // uintptr_t extra_rc : 8
            // # define RC_HALF  (1ULL<<7) 二进制表示为: 0b 1000,0000
            // extra_rc 总共 8 位,现在把它置为 RC_HALF,表示 extra_rc 溢出
            newisa.extra_rc = RC_HALF;
            // 把 has_sidetable_rc 标记为 true,表示 extra_rc 已经存不下该对象的引用计数,
            // 需要扩张到 SideTable 中
            newisa.has_sidetable_rc = true;
        
     while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));

    if (variant == RRVariant::Full) 
        if (slowpath(transcribeToSideTable)) 
            // Copy the other half of the retain counts to the side table.
            // 复制 retain count 的另一半到 SideTable 中。
            sidetable_addExtraRC_nolock(RC_HALF);
        
        // 如果 tryRetain 为 false 并且 sideTableLocked 为 true,则 SideTable 解锁
        if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
     else 
        ASSERT(!transcribeToSideTable);
        ASSERT(!sideTableLocked);
    
    // 返回 this
    return (id)this;

  • TaggedPointer:值存在指针内,直接返回。
  • !newisa.nonpointer:未优化的 isa ,使用sidetable_retain()
  • newisa.nonpointer:已优化的 isa , 这其中又分 extra_rc 溢出和未溢出的两种情况。
    ①未溢出时,isa.extra_rc + 1
    ②溢出时,将 isa.extra_rc 中一半值转移至sidetable中,然后将isa.has_sidetable_rc设置为true,表示使用了sidetable来计算引用次数

objc_release的内部逻辑

void 
objc_release(id obj)

    if (_objc_isTaggedPointerOrNil(obj)) return;
    return obj->release();

inline void
objc_object::release()

    ASSERT(!isTaggedPointer());

    rootRelease(true, RRVariant::FastOrMsgSend);

ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)

    if (slowpath(isTaggedPointer())) return false;
    // 标记 SideTable 是否加锁了
    bool sideTableLocked = false;
    // 临时变量存放旧的 isa 和字段修改后的 isa
    isa_t newisa, oldisa;
    // 以原子方式读到 &isa.bits 的数据
    oldisa = LoadExclusive(&isa.bits);

    if (variant == RRVariant::FastOrMsgSend) 
        // These checks are only meaningful for objc_release()
        // They are here so that we avoid a re-load of the isa.
        if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) 
            ClearExclusive(&isa.bits);
            if (oldisa.getDecodedClass(false)->canCallSwiftRR()) 
                swiftRelease.load(memory_order_relaxed)((id)this);
                return true;
            
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
            return true;
        
    

    if (slowpath(!oldisa.nonpointer)) 
        // a Class is a Class forever, so we can perform this check once
        // outside of the CAS loop
        if (oldisa.getDecodedClass(false)->isMetaClass()) 
            ClearExclusive(&isa.bits);
            return false;
        
    

retry:
    do 
        // 把 oldisa 赋值给 newisa,此时 isa.bits/oldisa/newisa 三者是相同的
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) 
            // 如果对象的 isa 只是原始指针 (Class isa/Class cls)
                        
            // __arm64__ && !__arm642__ 平台下,取消 &isa.bits 的独占访问标记
            // x86_64 下什么都不需要做,对它而言上面的 LoadExclusive 也只是一个原子读取 (atomic_load)
            ClearExclusive(&isa.bits);
            return sidetable_release(sideTableLocked, performDealloc);
        
        // 正在dealloc
        if (slowpath(newisa.isDeallocating())) 
            ClearExclusive(&isa.bits);
            if (sideTableLocked) 
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            
            return false;
        

        // don't check newisa.fast_rr; we already called any RR overrides
        // 不要检查 newisa.fast_rr; 我们之前已经调用过所有 RR 的重载
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        // 如果发生了下溢出的话,要进行处理,如果没有发生的话就是结束循环,解锁并执行 return false;
        if (slowpath(carry)) 
            // don't ClearExclusive()
            goto underflow;
        
        // 这里结束循环的方式同 rootRetain 函数,都是为了保证 isa.bits 能正确修改
        // StoreExclusive 和 StoreReleaseExclusive 的区别在于 memory_order_relaxed 和 memory_order_release
        // 可参考 https://en.cppreference.com/w/cpp/atomic/memory_order
            
        // 当 &isa.bits 与 oldisa.bits 相同时,把 newisa.bits 复制给 &isa.bits,并返回 true
        // 当 &isa.bits 与 oldisa.bits 不同时,
        // 把 oldisa.bits 复制给 &isa.bits, 并返回 false (此时会继续进行 do wehile 循环)
     while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));

    if (slowpath(newisa.isDeallocating()))
        goto deallocate;
    if (variant [OC学习笔记]自动引用计数

[OC学习笔记]自动引用计数初学

OC对象与CF对象的相互转换 和 ARC下查看OC对象的引用计数

[OC学习笔记]内存管理

Swift学习笔记-自动引用计数弱引用和无主引用

Swift学习笔记-自动引用计数弱引用和无主引用