本文介绍: 这里是与上一篇的ARC结合,ARC的规则讲述了在使用ARC过程需要注意的地方,使用ARC的某些原理,ARC的实现则是通过Clangobjc4库的源代码对ARC的实现过程代码进行一个详细的学习和了解。LLVM的编译过程还是需要结合一些网上的总结博客来看,至少我是自己借助了这些有用的资源才明白了一些。苹果官方称ARC是由编译期进行内存管理的,但实际上只有编译器是无法完全胜任的,在此基础还需要OC运行时库的帮助Clang (LLVM编译器) 3.0以上。

前言

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

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

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

简单了解Clangllvm

关于查看Clang编译的源代码

首先,需要下载Clang的源代码可以在LLVM官网下载源代码https://llvm.org/releases/download.htmlClang源代码包含了大量的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;  //->存储引用计数
    };
};

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

struct objc_object {
    isa_t isa;
};

objc_object就是 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
};

objc_class继承了objc_object结构如下

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)) {
            
            // 在 macarm64e 下不执行任何操作,只在 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;
}

上面的代码分成 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)) {
            // 未优化 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_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;
    }
	// 该地址没有值,正赋予新值,如果正在释放将会 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;
}
  • 通过弱引用指向的对象,获取弱引用表,并且将其上锁,防止在此期间被清除。
  • 判断是否包含自定义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的实现注重于编译器执行某些函数的过程,还是学到了很多,慢慢也能看懂一些代码了,就是实现过程繁琐,慢慢细细了解函数还是值得回味的。

原文地址:https://blog.csdn.net/weixin_61639290/article/details/129644725

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_5993.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注