[OC学习笔记]ARC与引用计数
Posted Billy Miracle
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[OC学习笔记]ARC与引用计数相关的知识,希望对你有一定的参考价值。
通过底层汇编看看ARC是怎么工作的
首先自定义一个类Test
。
该类有两个类方法,区别在于一个以new
开头,调用者持有返回的对象,而create
开头的则不持有。
这个也是对应了ARC的方法命名规则:
将内存管理语义在方法名中表示出来早已成为 Objective-C的惯例, 而ARC则将之确立为硬性规定。若方法名以下列词语开头, 则其返回的对象归调用者所有: alloc
、new
、copy
、mutableCopy
。若调用上述开头的方法就要负责释放返回的对象。也就是说, 这些对象在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_initWeak
和objc_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
销毁后, 指针仍然是指向那块内存, 所以是不安全的。正如其名,unretained
,unsafe
。
__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学习笔记]自动引用计数