这里是与上一篇的ARC结合,ARC的规则讲述了在使用ARC过程需要注意的地方,使用ARC的某些原理,ARC的实现则是通过Clang和objc4库的源代码对ARC的实现过程的代码进行一个详细的学习和了解。
LLVM的编译过程还是需要结合一些网上的总结博客来看,至少我是自己借助了这些有用的资源才明白了一些。
苹果官方称ARC是由编译期进行内存管理的,但实际上只有编译器是无法完全胜任的,在此基础还需要OC运行时库的帮助
首先,需要下载Clang的源代码。可以在LLVM官网上下载源代码:https://llvm.org/releases/download.html。
Clang源代码包含了大量的C++代码,简单的语法懂了就OK。
初始化一个变量,并且把这个对象赋值给另一个变量
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
objc_retain和objc_release
是 书上说的objc4库的方法。
在分析 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; //->存储引用计数};
};
其中nonpointer
、weakly_referenced
、has_sidetable_rc
和extra_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 个小分支
其实objt_retain函数执行的优先级是判断对象是否存在,不存在则直接返回,如果存在,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_rc
和sidetable
中,当isa.extra_rc
溢出时,将一半计数转移至sidetable
中,而当其下溢时,又会将计数转回**。当二者都为空时,会执行释放流程**
在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 里面,针对这个点区分一下上述函数。
看别的博客提到了优化流程这个过程,实现就是针对上述两个方法的源码的理解,这里简单的总结,看那么多源码头大。
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实现过程的探究
测试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
中存在多个对象共享一个弱引用表。我们知道__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;
}
注意
objc_loadWeakRetained
和objc_autorelease
函数的调用动作__weak
自身的原因直接被销毁导致的悬空指针.建议
规则里讲到,将对象赋值给附有__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);
}
这个过程和之前说过的很相像。书上的图
objc_autoreleasePoolPush
和objc_autoreleasePoolPop
这一对方法。__autoreleasing
修饰符转换成objc_autorelease,将obj加入自动释放池中。编译器对自动释放池的处理逻辑大致分成:
objc_autoreleasePoolPush
作为自动释放池作用域的第一个函数。objc_autorelease
将对象加入自动释放池。bjc_autoreleasePoolPop
作为自动释放池作用域的最后一个函数。为了了解自动释放池的原理,需要知道一些基础知识。
自动释放池都是由一个或者多个AutoreleasePoolPage
组成,page的 SIZE 为 4096 bytes ,它们通过parent
和child
指针组成一个双向链表。
这个源码后面会详细学。。。
这个简单,照着书讲一讲,别忘了
arc的实现注重于编译器执行某些函数的过程,还是学到了很多,慢慢也能看懂一些代码了,就是实现过程繁琐,慢慢细细了解函数还是值得回味的。