【iOS】ARC实现

ARC由以下工具来实现:

  • clang(LLVM编译器)3.0以上
  • objc4 Objective-C运行时库493.9以上

下面我们,我们将围绕clang汇编输出和objc4库的源代码探究ARC实现

1. __strong修饰符

1.1 赋值给附有__strong修饰符的变量

看下面代码

objectivec 复制代码
{
	id __strong obj = [[NSObject alloc] init];
}

实际上,该源代码可以转换为调用以下的函数。

objectivec 复制代码
// 编译器的模拟代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);

如原源代码所示,2次调用objc_msgSend方法,变量域结束时通过objc_release释放对象。

虽然ARC有效时不能使用release方法,但由此可知编译器自动插入了release。

1.2 使用alloc/new/copy/mutableCopy以外的方法

objectivec 复制代码
{
	id __strong obj = [NSMutableArray array];
}

转换如下:

objectivec 复制代码
// 编译器的模拟代码
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);

objc_retainAutoreleasedReturnValue()函数主要用于最优化程序运行。它用于自己持有(retained)对象的函数,但它持有的对象应为返回注册在autoreleasepool中对象的方法或是函数的返回值。像该源代码这样,在调用alloc/new/copy/mutableCopy以外的方法,由编译器插入该函数。

下面看看NSMutableArray类的array方法的源代码:

objectivec 复制代码
+ (id)array {
	return [[NSMutableArray alloc] init];
}

以下为该源代码的转换:

objectivec 复制代码
+ (id)array 
{
	id obj = objc_msgSend(NSMutableArray, @selector(alloc));
	objc_msgSend(obj, @selector(init));
	return objc_autoreleaseReturnValue(obj);
}

objc_autoreleaseReturnValue()函数用于alloc/new/copy/mutableCopy方法以外方法返回对象的实现上。像该源代码这样,返回注册到autoreleasepool中的对象。但是objc_autoreleaseReturnValue函数同objc_autorelease函数不同,一般不仅限于注册对象到autoreleasepool中。

在上例中,objc_autoreleaseReturnValue()函数会检查使用该函数的方法或函数调用方的执行命令列表,如果函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleasedReturnValue()函数,那么就不将返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方。

objc_retainAutoreleasedReturnValue()函数与objc_retain函数不同,即便不注册到autoreleasepool中而返回对象,也能正确的获取对象。

通过以上两个函数的协作,可以不将对象注册到autoreleasepool中而直接传递,这一过程达到了最优化。

2. __weak修饰符

2.1 赋值给附有__weak修饰符的变量;

objectivec 复制代码
{
	id _weak obj1 = obj;
}

假设变量obj附加__strong修饰符且对象被赋值。

objectivec 复制代码
// 编译器的模拟代码
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);

objc_initWeak函数将附有__weak修饰符的变量初始化为0后,会将赋值的对象作为参数调用objc_storeWeak函数。
objc_destroyWeak函数将0作为参数调用objc_storeWeak函数。

即前面的源代码与下列源代码相同:

objectivec 复制代码
// 编译器模拟代码
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);

objc_storeWeak函数把第二参数的赋值对象作为键值,将第一参数(附有__weak修饰符的变量)的地址注册到weak表中。如果第二参数为0,则把变量的地址从weak表中删除。
weak表与引用计数表相同,作为散列表实现。将废弃对象的地址作为键值进行检索,就能高速地获取对应的__weak修饰符的变量的地址。对于一个键值,可以注册多个变量的地址。

2.2 废弃谁都不持有的对象时,程序的动作;

(1) objc_release

(2) 因为引用计数为0所以执行dealloc

(3) _objc_rootDealloc

(4) object_dispose

(5) objc_destructInstanse

(6) objc_clear_deallocating

objc_clear_deallocating函数的动作如下:

  1. 从weak表中获取废弃对象的地址作为键值;
  2. 将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil;
  3. 从weak表中删除该记录;
  4. 从引用计数表中删除废弃对象的地址为键值的记录。

通过以上步骤,实现附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量的功能。

由此可知,如果大量使用附有__weak修饰符的变量,则会消耗相应的CPU资源。所以我们只在需要避免循环引用时使用__weak修饰符。

2.3 将自己生成并持有的对象赋值给附有__weak修饰符的变量中;

objectivec 复制代码
{
	id __weak obj = [[NSObject alloc] init];
}

因为附有__weak修饰符的变量不能持有对象,这时该对象会被释放并被废弃,因此会引起编译器警告。

我们看看编译器如何处理该源代码。

objectivec 复制代码
// 编译器的模拟代码
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_masSend(tmp, @selector(init));
objc_initWeak(&obj, tmp);
objc_release(tmp);
objc_destroyWeak(&object);

虽然自己生成并持有的对象被通过objc_initWeak函数被赋值给附有__weak修饰符的变量中,但是编译器判断其没有持有者,故该对象立即通过objc_release函数被释放和废弃。

2.4 其他

在《Objective-C 高级编程》这本书中描述,在ARC下,使用附有__weak修饰符的变量即是使用注册到autoreleasepool中的对象。实际上,可能由于我手上这一版太老,经过太多版本变迁。经我验证,至少在Xcode13.3.1中,这个说法已不适用。

看如下源代码:

objectivec 复制代码
    @autoreleasepool {
        id obj = [[NSObject alloc] init];
        
        {
            id __weak o = obj;
            NSLog(@"%@", o);
            NSLog(@"%@", o);
            NSLog(@"%@", o);
            _objc_autoreleasePoolPrint();
        }
    }

该源代码运行结果如下:

可以看到虽然多次使用了附有__weak修饰符的变量,但对象并没有被注册到autoreleasepool中。

稍作修改:

objectivec 复制代码
    @autoreleasepool {
        id obj = [[NSObject alloc] init];
        
        {
            id __weak o = obj;
            id __autoreleasing tmp = o;
            NSLog(@"%@", o);
            NSLog(@"%@", o);
            NSLog(@"%@", o);
            _objc_autoreleasePoolPrint();
        }
    }

运行结果如下:

可以看到,该对象只在运行id __autoreleasing tmp = o;时被注册到autoreleasepool中。

3. __autoreleasing修饰符

将对象赋值给__autoreleasing修饰符的变量等同于ARC无效时调用对象的autorelease方法。

3.1 赋值给附有__autoreleasing修饰符的变量;

objectivec 复制代码
    @autoreleasepool {
        id __autoreleasing obj = [[NSObject alloc] init];
    }

可做如下变换:

objectivec 复制代码
// 编译器模拟代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

虽然ARC有效和无效时,其在源代码上的表现有所不同,但autorelease的功能完全一样。

3.2 使用alloc/new/copy/mutableCopy以外的方法

objectivec 复制代码
    @autoreleasepool {
        id __autoreleasing obj = [NSMutableArray array];
    }

可做如下变换:

objectivec 复制代码
// 编译器的模拟代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutireleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

虽然持有对象的方法从alloc方法变为objc_retainAutireleasedReturnValue函数,但注册autoreleasepool的方法仍然是objc_autorelease函数。

4. ARC工作原理

ARC 的工作原理大致是这样:当我们编译源码的时候,编译器会分析源码中每个对象的生命周期,然后基于这些对象的生命周期,在合适的地方添加相应的引用计数操作代码retain, release和autorelease。

ARC 是工作在编译期的一种技术方案,这样的好处是:

  1. 编译之后,ARC 与非 ARC 代码是没有什么差别的,所以二者可以在源码中共存。实际上,你可以通过编译参数 -fno-objc-arc 来关闭部分源代码的 ARC 特性。
  2. 相对于垃圾回收这类内存管理方案,ARC 不会带来运行时的额外开销,所以对于应用的运行效率不会有影响。相反,由于 ARC 能够深度分析每一个对象的生命周期,它能够做到比人工管理引用计数更加高效。例如在一个函数中,对一个对象刚开始有一个引用计数 +1的操作,之后又紧接着有一个 -1 的操作,那么编译器就可以把这两个操作都优化掉。

ARC在编译期和运行期做了什么?

  1. 在编译期,ARC会把互相抵消的retain、release、autorelease操作约简。
  2. ARC包含有运行期组件,可以在运行期检测到autorelease和retain这一对多余的操作。为了优化代码,在方法中返回自动释放的对象时,要执行一个特殊函数。
相关推荐
Topstip13 分钟前
iOS 19 重大更新泄露,将带来更“聪明”的 Siri 挑战 ChatGPT
人工智能·ios·ai·chatgpt
William.csj1 小时前
Mac——鼠标增强插件Mos
macos·mac
凄凄迷人4 小时前
如何调试 chrome 崩溃日志(MAC)
前端·chrome·macos·crash
钢门狂鸭4 小时前
mvn-mac操作小记
macos·maven
疯狂的沙粒4 小时前
mac 安装node提示 nvm install v14.21.3 failed可能存在问题
macos
赶路人儿4 小时前
IntelliJ IDEA配置(mac版本)
java·macos·intellij-idea
木有会4 小时前
【mac】mac自动定时开关机和其他常用命令,管理电源设置的工具pmset
macos
木有会5 小时前
【mac】终端左边太长处理,自定义显示名称(terminal路径显示特别长)
macos
_im.m.z6 小时前
Mac配置和启动 Tomcat
java·macos·tomcat·ssm框架
丁总学Java6 小时前
在 Mac(ARM 架构)上安装 JDK 8 环境
arm开发·macos·架构