前言
这里是与上一篇的ARC结合,ARC的规则讲述了在使用ARC过程需要注意的地方,使用ARC的某些原理,ARC的实现则是通过Clang和objc4库的源代码对ARC的实现过程的代码进行一个详细的学习和了解。
LLVM的编译过程还是需要结合一些网上的总结博客来看,至少我是自己借助了这些有用的资源才明白了一些。
苹果官方称ARC是由编译期进行内存管理的,但实际上只有编译器是无法完全胜任的,在此基础还需要OC运行时库的帮助
简单了解Clang 和 llvm
- LLVM是构架编译器(compiler)的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序译时间(compile-time)、链接时间(link–time)、运行时间(run–time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。
- LLVM 是一个总括项目,承载并开发了一组紧密结合的低级工具链组件(例如,汇编器,编译器,调试器等),这些组件被设计为兼容的。
- 尽管LLVM提供了一些独特的功能,并且以其一些出色的工具而闻名(例如,Clang编译器,C / C ++ / Objective-C编译器,它比GCC编译器具有许多优势),但它是LLVM与其他编译器不同的是其内部体系结构。
- Clang是LLVM项目的一个子项目,基于LLVM架构的C/C++/Objective-C编译器前端。
关于查看Clang编译的源代码
首先,需要下载Clang的源代码。可以在LLVM官网上下载源代码:https://llvm.org/releases/download.html。
Clang源代码包含了大量的C++代码,简单的语法懂了就OK。
__strong
void defaultFunction() {
id __strong obj0 = [[NSObject new];
id __strong obj1 = obj0;
}
打断点,汇编告诉我们发生了什么
可以看到在执行的过程中调用了四次函数,针对LLVM的变编译过程进行分析
void defaultFunction() {
id obj0 = obj_msgSend(NSObject, @selector(new));
id obj1 = objc_retain(obj0)
objc_storeStrong(obj0, null);
objc_storeStrong(obj1, null);
}
上面我用obj_msgSend
代替了objc_opt_new
方法是因为在objc_opt_new
方法原型里面是调用的obj_msgSend
objc_opt_new
obj_msgSend(NSObject, @selector(new))
简单理解就是新建一个对象,然后在objc_opt_new
方法的最后返回这个新对象。
id
objc_opt_new(Class cls)
{
#if __OBJC2__
if (fastpath(cls && !cls->ISA()->hasCustomCore())) {
return [callAlloc(cls, false/*checkNil*/) init];
}
#endif
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(new));
}
objc_storeStrong
继续对四次函数分析,也就是先创建对象,然后引用计数加一,然后分别释放。这一个过程都是objc_storeStrong
函数完成的
看看它做了什么
这个函数在规则的开始引入过,就是完成了一个初始化,引用计数加1retain
,指针指向对象,当对象超出变量作用域的时候销毁release
objc_retain和objc_release
是 书上说的objc4库的方法。
isa指针
在分析 ARC 相关源码之前,需要对 isa 有一定了解(搬运别人的注释总结,自己看不懂),其中存储了一些非常重要的信息,下面是 isa 的结构组成:
union isa_t
{
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;//->表示使用优化的isa指针
uintptr_t has_assoc : 1;//->是否包含关联对象
uintptr_t has_cxx_dtor : 1;//->是否设置了析构函数,如果没有,释放对象更快
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->类的指针
uintptr_t magic : 6;//->固定值,用于判断是否完成初始化
uintptr_t weakly_referenced : 1;//->对象是否被弱引用
uintptr_t deallocating : 1;//->对象是否正在销毁
uintptr_t has_sidetable_rc : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
uintptr_t extra_rc : 19; //->存储引用计数
};
};
其中nonpointer
、weakly_referenced
、has_sidetable_rc
和extra_rc
都是 ARC 有直接关系的成员变量,其他的大多也有涉及到。
struct objc_object {
isa_t isa;
};
struct objc_class : objc_object {
isa_t isa; // 继承自 objc_object
Class superclass;
cache_t cache; // 方法实现缓存和 vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
};
isa
:objc_object 指向类,objc_class 指向元类。superclass
:指向父类。cache
:存储用户消息转发优化的方法缓存和 vtable 。bits
:class_rw_t 和 class_ro_t ,保存了方法、协议、属性等列表和一些标志位。
objc_retain
objc_retain,看他的后缀,应该是和引用计数有关的函数,并且是+1。
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
跳转到obj->retain()
发现了rootRetain方法,其中再点下去就是一些细化的方法。
这个方法看起来很复杂,结合前面的isa结构体一起看
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 溢出和未溢出的两种情况。
- extra_rc:当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc
-
- 未溢出时,isa.extra_rc + 1 完事。
其实objt_retain函数执行的优先级是判断对象是否存在,不存在则直接返回,如果存在,isTaggedPointer
函数判断是否为TaggedPointer
objc_release
在一个对象初始化超过作用域的时候,就会调用objc_release方法,这里探究一下objc_release方法的内部逻辑
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
大体的框架和Objc_retain方法很相似,也是分为三步走。
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)) {
// 未优化 isa
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
// 入参是否要执行 Dealloc 函数,如果为 true 则执行 SEL_dealloc
return sidetable_release(performDealloc);
}
// extra_rc --
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// donot ClearExclusive()
goto underflow;
}
// 更新 isa 值
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
// 处理下溢,从 side table 中借位或者释放
newisa = oldisa;
// 如果使用了 sidetable_rc
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
// 调用本函数处理下溢
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// 从 sidetable 中借位引用计数给 extra_rc
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
if (borrowed > 0) {
// extra_rc 是计算额外的引用计数,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);
}
}
}
// 如果还是保存失败,则还回 side table
if (!stored) {
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
// 没有使用 sidetable_rc ,或者 sidetable_rc 计数 == 0 的就直接释放
// 如果已经是释放中,抛个过度释放错误
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
// 更新 isa 状态
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
// 执行 SEL_dealloc 事件
__sync_synchronize();
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
}
引用计数分别保存在isa.extra_rc
和sidetable
中,当isa.extra_rc
溢出时,将一半计数转移至sidetable
中,而当其下溢时,又会将计数转回**。当二者都为空时,会执行释放流程**
alloc等方法的过程
在MRC情况下,遵循谁创建谁释放的原则,alloc、new、copy和mutableCopy等方法创建的对象需要我们手动的释放,但是现在ARC下不需要我们手动的释放,书上用两段代码引入了两个方法。
objc_retainAutoreleasedReturnValue
void objcAuto1() {
id __strong obj = [NSMutableArray array];
}
调用Array方法,汇编如下
模拟中间代码
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj)
objc_release(obj);
objc_retainAutoreleasedReturnValue函数主要用于与程序优化和运行,它是用于自己持有对象的函数。它持有的对象应该是返回注册在autoPool中的对象的方法,或者是函数的返回值,当调用alloc、new、copy和mutableCopy等外的方法,编译器自动插入该函数。
objc_autoreleaseReturnValue
与之相对的alloc方法存在另一个函数objc_autoreleaseReturnValue,它用于调用alloc、new、copy和mutableCopy之外的其他类方法的返回实现里
NSMutableArray* objcAuto2() {
return [[NSMutableArray alloc] init];
}
模拟代码
id obj = objc_msgSend(objc_msgSend(NSMutableArray, @selector(alloc)), @selector(init));
objc_autoreleaseReturnValue(obj);
return obj;
二者的区别和联系
再次说明 编译器会自动检查方法名,非alloc、new、copy和mutableCopy则自动注册到auto Pool 里面,针对这个点区分一下上述函数。
- 区别
-
- objc_autoreleaseReturnValue虽然在上述代码返回的是注册到autoPool的对象,但是 objc_autoreleaseReturnValue不仅限于这一点,这是区别objc_autorelease和它的主要观点
- 联系
方法的优化流程
看别的博客提到了优化流程这个过程,实现就是针对上述两个方法的源码的理解,这里简单的总结,看那么多源码头大。
- 为了节省了一个将对象注册到
autoreleasePool
的操作,在执行objc_autoreleaseReturnValue
时,根据查看后续调用的方法列表是否包含objc_retainAutoreleasedReturnValue
方法,以此判断是否走优化流程 - 在执行
objc_autoreleaseReturnValue
时,优化流程将一个标志位存储在 TLS (Thread Local Storage) 中后直接返回对象。 - 执行后续方法
objc_retainAutoreleasedReturnValue
时检查 TLS 的标志位判断是否处于优化流程,如果处于优化流程中则直接返回对象,并且将 TLS 的状态还原。
最终优化流程相当于:
id obj = objc_msgSend(objc_msgSend(NSMutableArray, @selector(alloc)), @selector(init));
objc_release(obj);
未优化流程相当于:
id obj = objc_msgSend(objc_msgSend(NSMutableArray, @selector(alloc)), @selector(init));
objc_autorelease(obj);
objc_retain(obj);
objc_release(obj);
__weak修饰符的实现过程探究
weak 表示弱引用,使用的时候引用计数不会增加。在原对象释放后,弱引用变量也会随之被清除。
以三段代码展示__weak实现过程的探究
测试1
void weakFunction() {
id __weak obj = [NSObject new];
}
看看实现过程
weakFunction() {
id temp = objc_msgSend(NSObject, @selector(new));
objc_initWeak(&obj, temp);
objc_release(temp);
objc_destroyWeak(obj);
}
在该方法中声明 __weak
对象后并没有使用到,在objc_initWeak
后,立即释放调用了objc_release和objc_destroyWeak方法。符合__weak的特点
测试2
void weak1Function() {
id obj = [NSObject new];
id __weak obj1 = obj;
}
weak1Function() {
id obj = objc_msgSend(NSObject, @selector(new));
objc_initWeak(&obj1, obj);
objc_destroyWeak(obj1);
objc_storeStrong(obj, null);
}
该方法中obj是强引用,obj1是弱引用,objc_initWeak、 objc_destroyWeak先后成对调用,对应着弱引用变量的初始化和释放方法。
测试三
void weak2Function() {
id obj = [NSObject new];
id __weak obj1 = obj;
NSLog(@"%@", obj1);
}
weak2Function() {
id obj = objc_msgSend(NSObject, @selector(new));
objc_initWeak(obj1, obj);
id temp = objc_loadWeakRetained(obj1);
NSLog(@"%@",temp);
objc_release(temp);
objc_destroyWeak(obj1);
objc_storeStrong(obj, null);
}
和weak1Function不同之处是使用了弱引用变量obj1,在使用弱引用变量之前,编译器创建了一个临时的强引用对象,在用完后立即释放。
objc_initWeak 和 objc_destroyWeak
objc_initWeak
// location指针obj1 , newObj原始对象obj
id objc_initWeak(id *location, id newObj) {
// 查看原始对象实例是否有效
// 无效对象直接导致指针释放
if (!newObj) {
*location = nil;
return nil;
}
// 该地址没有值,正赋予新值,如果正在释放将会 crash
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
可以看出,这个函数仅仅是一个深层函数的调用入口,而一般的入口函数中,都会做一些简单的判断(例如 objc_msgSend 中的缓存判断),这里判断了其指针指向的类对象是否有效,无效直接释放,不再往深层调用函数。否则,object将被注册为一个指向value的__weak对象。而这事应该是objc_storeWeak函数干的
objc_destroyWeak
void objc_destroyWeak(id *location) {
// 该地址有值,没有赋予新值,如果正在释放不 crash
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
objc_storeWeak
storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj);
enum HaveOld { DontHaveOld = false, DoHaveOld = true }; // 是否有值
enum HaveNew { DontHaveNew = false, DoHaveNew = true }; // 是否有新值
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
}; // 操作正在释放中的对象是否 Crash
objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
objc_storeWeak
函数把第二个参数–赋值对象(value)的内存地址作为键值key,
将第一个参数 __weak
修饰的属性变量(location)的内存地址(&location)作为value,注册到 weak 表中。
这里还有objc_storeWeak
的实现代码太过于繁琐,以及引入了更深的源代码,我就没有继续向下看,在这里总结objc_storeWeak
函数做了什么
// HaveOld: true - 变量有值
// false - 需要被及时清理,当前值可能为 nil
// HaveNew: true - 需要被分配的新值,当前值可能为 nil
// false - 不需要分配新值
// CrashIfDeallocating: true - 说明 newObj 已经释放或者 newObj 不支持弱引用,该过程需要暂停
// false - 用 nil 替代存储
template bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
// 该过程用来更新弱引用指针的指向
// 初始化 previouslyInitializedClass 指针
Class previouslyInitializedClass = nil;
id oldObj;
// 声明两个 SideTable
// ① 新旧散列创建
SideTable *oldTable;
SideTable *newTable;
// 获得新值和旧值的锁存位置(用地址作为唯一标示)
// 通过地址来建立索引标志,防止桶重复
// 下面指向的操作会改变旧值
retry:
if (HaveOld) {
// 更改指针,获得以 oldObj 为索引所存储的值地址
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (HaveNew) {
// 更改新值指针,获得以 newObj 为索引所存储的值地址
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 加锁操作,防止多线程中竞争冲突
SideTable::lockTwoHaveOld, HaveNew>(oldTable, newTable);
// 避免线程冲突重处理
// location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改
if (HaveOld && *location != oldObj) {
SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
goto retry;
}
// 防止弱引用间死锁
// 并且通过 +initialize 初始化构造器保证所有弱引用的 isa 非空指向
if (HaveNew && newObj) {
// 获得新对象的 isa 指针
Class cls = newObj->getIsa();
// 判断 isa 非空且已经初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) {
// 解锁
SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
// 对其 isa 指针进行初始化
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// 如果该类已经完成执行 +initialize 方法是最理想情况
// 如果该类 +initialize 在线程中
// 例如 +initialize 正在调用 storeWeak 方法
// 需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记
previouslyInitializedClass = cls;
// 重新尝试
goto retry;
}
}
// ② 清除旧值
if (HaveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// ③ 分配新值
if (HaveNew) {
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
(id)newObj, location,
CrashIfDeallocating);
// 如果弱引用被释放 weak_register_no_lock 方法返回 nil
// 在引用计数表中设置若引用标记位
if (newObj && !newObj->isTaggedPointer()) {
// 弱引用位初始化操作
// 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用
newObj->setWeaklyReferenced_nolock();
}
// 之前不要设置 location 对象,这里需要更改指针指向
*location = (id)newObj;
}
else {
// 没有新值,则无需更改
}
SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
return (id)newObj;
}
- 从全局的哈希表
SideTables
中,利用对象本身地址进行位运算后得到对应下标,取得该对象的弱引用表。SideTables
是一个 64 个元素长度的散列表,发生碰撞时,可能一个SideTable
中存在多个对象共享一个弱引用表。 - 如果有分配新值,则检查新值对应的类是否初始化过,如果没有,则就地初始化。
- 如果 location 有指向其他旧值,则将旧值对应的弱引用表进行注销。
- 如果分配了新值,将新值注册到对应的弱引用表中。将isa.weakly_referenced设置为true,表示该对象是有弱引用变量,释放时要去清空弱引用表。
objc_loadWeakRetained
我们知道__weak生成的是非自己持有的对象实例,对象会被立即释放。
在上面的weak2函数里面,我们打印了弱引用的对象,这样子的话编译器为了保存到对象到我们打印的时候,就会令对象一直存在,知道不需要他。
在使用弱引用变量之前,编译器创建了一个临时的强引用对象,以此保证使用时不会因为被释放导致出错,在用完后立即释放。
id objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// 得到弱引用指针指向对象
obj = *location;
if (!obj) return nil;
if (obj->isTaggedPointer()) return obj; // TaggedPointer 直接返回
// 得到对应 weak_table
table = &SideTables()[obj];
// 如果被引用对象在此期间发生变化则重试
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
// 类和超类没有自定义 retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference 等方法
assert(cls->isInitialized());
// 尝试 retain
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
// 获取自定义 SEL_retainWeakReference 方法
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
class_getMethodImplementation(cls, SEL_retainWeakReference);
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
}
// 调用自定义函数
else if (! (*tryRetain)(obj, SEL_retainWeakReference)) {
result = nil;
}
}
else {
// 类未初始化,则初始化后回到 retry 重新执行
table->unlock();
_class_initialize(cls);
goto retry;
}
}
table->unlock();
return result;
}
注意
- 在使用附有__weak修饰符变量的情形下,需要注意
objc_loadWeakRetained
和objc_autorelease
函数的调用动作 -
- objc_autorelease函数将对象注册到autoreleasPool中。这是为了防止__waek修饰的变量因为
__weak
自身的原因直接被销毁导致的悬空指针.
- objc_autorelease函数将对象注册到autoreleasPool中。这是为了防止__waek修饰的变量因为
建议
__ autoreleaseing修饰符
规则里讲到,将对象赋值给附有__autoreleasing修饰符的变量等同于ARC无效的时候调用对象的autorelease方法。
void autoReleasingFunction() {
@autoreleasepool {
__autoreleasing id obj = [NSObject new];
}
}
void autoReleasingFunction() {
id token = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(new));
objc_autorelease(obj);
objc_autoreleasePoolPop(token);
}
这个过程和之前说过的很相像。书上的图
- @autoreleasepool{}关键字通过编译器转换成
objc_autoreleasePoolPush
和objc_autoreleasePoolPop
这一对方法。 __autoreleasing
修饰符转换成objc_autorelease,将obj加入自动释放池中。
- 由
objc_autoreleasePoolPush
作为自动释放池作用域的第一个函数。 - 使用
objc_autorelease
将对象加入自动释放池。 - 由o
bjc_autoreleasePoolPop
作为自动释放池作用域的最后一个函数。
实现探究
自动释放池都是由一个或者多个AutoreleasePoolPage
组成,page的 SIZE 为 4096 bytes ,它们通过parent
和child
指针组成一个双向链表。
这个源码后面会详细学。。。
引用计数
这个简单,照着书讲一讲,别忘了
总结
arc的实现注重于编译器执行某些函数的过程,还是学到了很多,慢慢也能看懂一些代码了,就是实现过程繁琐,慢慢细细了解函数还是值得回味的。
原文地址:https://blog.csdn.net/weixin_61639290/article/details/129644725
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_5993.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!