【iOS开发-ARC实现-源码略读】
创始人
2025-05-31 03:57:58
0

文章目录

  • 前言
  • 简单了解Clang 和 llvm
      • 关于查看Clang编译的源代码
  • __strong
          • **`objc_opt_new`**
          • **`objc_storeStrong`**
          • isa指针
          • **`objc_retain`**
          • `objc_release`
          • alloc等方法的过程
          • `objc_retainAutoreleasedReturnValue`
          • `objc_autoreleaseReturnValue`
            • 二者的区别和联系
          • 方法的优化流程
  • __weak修饰符的实现过程探究
          • `objc_initWeak 和 objc_destroyWeak`
          • objc_loadWeakRetained
  • __ autoreleaseing修饰符
          • 实现探究
  • 引用计数
  • 总结

前言

这里是与上一篇的ARC结合,ARC的规则讲述了在使用ARC过程需要注意的地方,使用ARC的某些原理,ARC的实现则是通过Clang和objc4库的源代码对ARC的实现过程的代码进行一个详细的学习和了解。

LLVM的编译过程还是需要结合一些网上的总结博客来看,至少我是自己借助了这些有用的资源才明白了一些。

苹果官方称ARC是由编译期进行内存管理的,但实际上只有编译器是无法完全胜任的,在此基础还需要OC运行时库的帮助

  • Clang (LLVM编译器) 3.0以上
  • objc4 ObjectiveC运行时库493.9以上。

简单了解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];}
#endifreturn ((id(*)(id, SEL))objc_msgSend)(cls, @selector(new));
}
objc_storeStrong

继续对四次函数分析,也就是先创建对象,然后引用计数加一,然后分别释放。这一个过程都是objc_storeStrong函数完成的

看看它做了什么请添加图片描述
这个函数在规则的开始引入过,就是完成了一个初始化,引用计数加1retain,指针指向对象,当对象超出变量作用域的时候销毁release

  • 检查输入的 obj 地址 和指针指向的地址是否相同。
  • 持有对象,引用计数 + 1 。
  • 指针指向 obj。
  • 原来指向的对象引用计数 - 1。

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设置成1uintptr_t extra_rc          : 19;  //->存储引用计数};
};

其中nonpointerweakly_referencedhas_sidetable_rcextra_rc都是 ARC 有直接关系的成员变量,其他的大多也有涉及到。

struct objc_object {isa_t isa;
};

objc_object就是 isa 基础上一层封装。

struct objc_class : objc_object {isa_t isa; // 继承自 objc_objectClass superclass;cache_t cache; // 方法实现缓存和 vtableclass_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
};

objc_class继承了objc_object,结构如下

  • 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 之前的 isaisa_t oldisa;// 记录 objc_object 修改后的 isaisa_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 loopif (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 );// 定义于头文件 // 原子地比较 obj 所指向对象的内存的内容与 expected 所指向的内存的内容,若它们相等,则以 desired 替换前者(进行读修改写操作)。// 否则,将 obj 所指向的实际内存内容加载到 *expected (进行加载操作)。do {// 默认不需要转移引用计数到 SideTabletranscribeToSideTable = 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),则返回 nilif (slowpath(newisa.isDeallocating())) {ClearExclusive(&isa.bits);// SideTable 处于加锁状态if (sideTableLocked) {ASSERT(variant == RRVariant::Full);// 进行解锁sidetable_unlock();}// 需要 tryRetainif (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 置为 truesideTableLocked = 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);}// 返回 thisreturn (id)this;
}

上面的代码分成 3 个小分支

  • 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 完事。
    • 溢出时,将 isa.extra_rc 中一半值转移至sidetable中,然后将isa.has_sidetable_rc设置为true,表示使用了sidetable来计算引用次数。

其实objt_retain函数执行的优先级是判断对象是否存在,不存在则直接返回,如果存在,isTaggedPointer函数判断是否为TaggedPointer

  • isTaggedPointer函数就是判断指针的值是否是我们需要的值,如果是TaggedPointer就直接从指针获取对应的值。然后返回
  • 最后执行rootRetain,就是上述方法。
objc_release

在一个对象初始化超过作用域的时候,就会调用objc_release方法,这里探究一下objc_release方法的内部逻辑

objc_release(id obj)
{if (!obj) return;if (obj->isTaggedPointer()) return;return obj->release();
}

大体的框架和Objc_retain方法很相似,也是分为三步走。

  • objc_object::rootRelease(bool performDealloc, bool handleUnderflow)方法
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)) {// 未优化 isaClearExclusive(&isa.bits);if (sideTableLocked) sidetable_unlock();// 入参是否要执行 Dealloc 函数,如果为 true 则执行 SEL_deallocreturn 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_rcif (slowpath(newisa.has_sidetable_rc)) {if (!handleUnderflow) {// 调用本函数处理下溢ClearExclusive(&isa.bits);return rootRelease_underflow(performDealloc);}// 从 sidetable 中借位引用计数给 extra_rcsize_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);if (borrowed > 0) {// extra_rc 是计算额外的引用计数,0 即表示被引用一次newisa.extra_rc = borrowed - 1;  // redo the original decrement toobool 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 tableif (!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_rcsidetable中,当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和它的主要观点
    • objc_retainAutoreleaseReturnValue()函数即便不注册到auto Pool中而返回对象也能正确的获取对象,与objc_retain的区别在这
  • 联系
    • objc_autoreleaseReturnValue会检查该函数的方法或者函数调用方的执行列表,如果在调用了该方法后紧接着调用objc_retainAutoreleaseReturnValue()函数,那么就不将返回的对象注册到autoPool中,而是直接传递到函数或者方法的调用方。二者通过协作可以不需要把对象注册到auto pool中而直接完成传递给方法/函数的调用者
      请添加图片描述
方法的优化流程

看别的博客提到了优化流程这个过程,实现就是针对上述两个方法的源码的理解,这里简单的总结,看那么多源码头大。

  • 为了节省了一个将对象注册到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,在使用弱引用变量之前,编译器创建了一个临时的强引用对象,在用完后立即释放。

知道了__weak实现过程的函数和调用,接下来展开看看

objc_initWeak 和 objc_destroyWeak
  • objc_initWeak
// location指针obj1 , newObj原始对象obj
id objc_initWeak(id *location, id newObj) {
// 查看原始对象实例是否有效
// 无效对象直接导致指针释放if (!newObj) {*location = nil;return nil;}// 该地址没有值,正赋予新值,如果正在释放将会 crashreturn storeWeak(location, (objc_object*)newObj);
}

可以看出,这个函数仅仅是一个深层函数的调用入口,而一般的入口函数中,都会做一些简单的判断(例如 objc_msgSend 中的缓存判断),这里判断了其指针指向的类对象是否有效,无效直接释放,不再往深层调用函数。否则,object将被注册为一个指向value的__weak对象。而这事应该是objc_storeWeak函数干的

  • objc_destroyWeak
void objc_destroyWeak(id *location) {// 该地址有值,没有赋予新值,如果正在释放不 crash(void)storeWeak(location, nil);
}
  • objc_storeWeak
storeWeak (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_tabletable = &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());// 尝试 retainif (! 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;
}
  • 通过弱引用指向的对象,获取弱引用表,并且将其上锁,防止在此期间被清除。
  • 判断是否包含自定义retain方法,如果没有,则使用默认rootTryRetain方法,使引用计数 + 1
    • 如果使用了自定义retain方法,则调用自定义方法,在调用之前会先判断该对象所属类是否已经初始化过,如果没有初始化会先进行初始化然后再调用。

注意

  • 在使用附有__weak修饰符变量的情形下,需要注意objc_loadWeakRetainedobjc_autorelease函数的调用动作
    • objc_loadWeakRetained函数取出所有的__weak修饰符变量引用的对象并retain
    • objc_autorelease函数将对象注册到autoreleasPool中。这是为了防止__waek修饰的变量因为__weak自身的原因直接被销毁导致的悬空指针.

建议

  • 因此在使用__weak修饰符的变量的时候最好先赋值给__strong修饰符的变量后在使用。而且这样做只会注册到autoPool一次,而不是使用多少次,注册多少次。

__ autoreleaseing修饰符

规则里讲到,将对象赋值给附有__autoreleasing修饰符的变量等同于ARC无效的时候调用对象的autorelease方法。

还是通过LLVM生成的源代码对过程分析

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_autoreleasePoolPushobjc_autoreleasePoolPop这一对方法。
  • __autoreleasing 修饰符转换成objc_autorelease,将obj加入自动释放池中。

编译器对自动释放池的处理逻辑大致分成:

  • objc_autoreleasePoolPush作为自动释放池作用域的第一个函数。
  • 使用objc_autorelease将对象加入自动释放池。
  • 由objc_autoreleasePoolPop作为自动释放池作用域的最后一个函数。
实现探究

为了了解自动释放池的原理,需要知道一些基础知识。

自动释放池都是由一个或者多个AutoreleasePoolPage组成,page的 SIZE 为 4096 bytes ,它们通过parentchild指针组成一个双向链表。

这个源码后面会详细学。。。

引用计数

这个简单,照着书讲一讲,别忘了

总结

arc的实现注重于编译器执行某些函数的过程,还是学到了很多,慢慢也能看懂一些代码了,就是实现过程繁琐,慢慢细细了解函数还是值得回味的。

相关内容

热门资讯

Oracle11g Sessi... 修改参数前,备份参数文件! 修改参数前,备份参数文件&#x...
蓝桥杯每日一真题—— [蓝桥杯... 文章目录[蓝桥杯 2021 省 AB2] 完全平方数题目描述输入格式输出格式样例 #1样例输入 #1...
医德医风心得体会 2022医德医风心得体会(精选12篇)  某些事情让我们心里有了一些心得后,将其记录在心得体会里,让...
朝花夕拾读书的心得体会200... 朝花夕拾读书的心得体会200字(通用15篇)  从某件事情上得到收获以后,可以寻思将其写进心得体会中...
AndroidMvvMFram... 文档下面会对框架中所使用的一些核心技术进行阐述。该框架作为技术积累的产物,会一直更新维...
Go_反射的使用 反射可以在运行时动态地检查变量和类型,并且可以调用变量和类型的方法、获取和修改变量的值...
小学生品德教育的心得体会 小学生品德教育的心得体会(通用14篇)  当在某些事情上我们有很深的体会时,马上将其记录下来,它可以...
读书的心得体会 关于读书的心得体会范文(精选25篇)  当我们备受启迪时,往往会写一篇心得体会,这样可以记录我们的思...
谁动了我的奶酪读书心得 谁动了我的奶酪读书心得(精选10篇)  认真读完一本著作后,相信大家都增长了不少见闻,何不写一篇读书...
数据结构——查找 查找概论         查找就是根据给定的某个值,在查找表中确定一个其关键字等于给...
初一军训心得 初一军训心得  8月20号至8月24号是我们xx附中在xx教育基地军训的日子,初一军训心得。在这短短...
Linux之进程信号 目录 一、生活中的信号 背景知识 生活中有没有信号的场景呢? 是不是只有这些场景真正的...
【计算机视觉】经典的图卷积网络... 【计算机视觉】经典的图卷积网络框架(LeNet、AlexNet、VGGNet、Ince...
质量的心得体会 质量的心得体会(通用15篇)  我们有一些启发后,马上将其记录下来,这样可以记录我们的思想活动。那么...
志愿服务心得体会 志愿服务心得体会(精选6篇)  我们得到了一些心得体会以后,往往会写一篇心得体会,从而不断地丰富我们...
【算法】动态规划复习汇总 一、概述 1.1 基本概念 动态规划主要用于解决多段决策最优化的问题。 动态规划通常用来解决这样的一...
《中国史纲》读书心得 《中国史纲》读书心得  内容简介:  张荫麟(1905—1942),号素痴,亦常作笔名。著名学者;历...
思想大解放个人心得体会 思想大解放个人心得体会  通过单位进行的解放思想的大讨论后,我感知此次活动具有极其重要的意义。没有新...
golang 调试工具dlv ... 应用dlv debug **.go 在Golang中,dlv是一个常用的调试工具&#x...
劳动课程学习的心得体会 劳动课程学习的心得体会(通用20篇)  我们从一些事情上得到感悟后,不如来好好地做个总结,写一篇心得...