ARC?
ARC即自动引用计数。具体介绍以及ARC规则可以看我之前写的另外一篇文章——ARC规则
本篇主要探究ARC在背后究竟做了什么?
先下结论,后续都是围绕这个展开的。
1.在编译期,ARC会把互相抵消的retain、release、autorelease操作约简。
2.ARC包含有运行期组件,可以在运行期检测到autorelease和retain这一对多余的操作。为了优化代码,在方法中返回自动释放的对象时,要执行一个特殊函数。
ARC简化引用计数
在Clang编译器项目带有一个静态分析器(static analyzer)
用于指名程序里引用计数出问题的地方。
在使用ARC
时一定要记住, **引用计数实际上还是要执行的, 只不过保留与释放操作现在是由ARC自动为你添加。**由于ARC会自动执行 retain、release、autorelease、decalloc
等操作, 所以直接在ARC下调用这些内存管理方法是非法的。
实际上, ARC
在调用这些方法时, 并不通过普通的 Objective-C消息派发机制,而是直接调用其底层C语言版本。这样做性能更好, 因为保留及释放操作需要频繁执行, 所以直接调用底层函数能节省很多CPU周期。
通过底层汇编看看ARC是怎么工作的
首先自定义一个类Test
。
该类有两个类方法,区别在于一个以new
开头,调用者持有返回的对象,而create
开头的则不持有。
这个也是对应了ARC
的方法命名规则:
将内存管理语义在方法名中表示出来早已成为 Objective-C的惯例, 而ARC
则将之确立为硬性规定。若方法名以下列词语开头, 则其返回的对象归调用者所有: alloc、new、copy、mutable Copy
。若调用上述开头的方法就要负责释放返回的对象。也就是说, 这些对象在MRC
中需要你手动的进行释放。若方法名不以上述四个词语开头, 返回的对象就不需要你手动去释放, 因为在方法内部将会自动执行一次autorelease方法。
//
// Test.h
// TestArc
//
// Created by 差不多先生 on 2022/7/18.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Test : NSObject
+ (instancetype)createTest;
+ (instancetype)newTest;
@end
NS_ASSUME_NONNULL_END
#import "Test.h"
@implementation Test
+ (instancetype)createTest {
return [[self alloc] init];
}
+ (instancetype)newTest {
return [[self alloc] init];
}
@end
测试的情景:
#import "Test.h"
@interface ViewController ()
@property (nonatomic, strong) Test* test;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// 自己持有返回对象
[Test newTest]; // test1
// id temp = [Test newTest]; // test2
// self.test = [Test newTest]; // 3
// // 非自己持有返回对象
[Test createTest]; // 4
// id temp2 = [Test createTest]; // 5
// self.test = [Test createTest]; // 6
}
Test1
现在开始查看底层的汇编语言,可以通过编译器的debug调试。
可以看见主要出现了20 21 行的两个方法。
objc_msgSend:
这个就是向Test发送消息newTest。
objc_release:
release的底层版本,释放newTest返回的对象。
可以看出ARC为我们自动添加了release操作,ARC自动添加代码应该如下:
id temp = [Test newTest];
objc_release(temp) ;
Test2
可以看到这次出现了一个新的底层函数objc_storeStrong:
,相当于ARC在底层添加了
objc_storeStrong(&temp1, nil);
看看objc库中其具体实现。
// strong
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
上面的代码做了以下四件事情:
- 检查输入的 obj 地址 和指针指向的地址是否相同。
- 持有对象,引用计数 + 1 。
- 指针指向 obj。
- 原来指向的对象引用计数 - 1。
在本例子中相当于temp1持有newTest返回的对象,并且引用计数 + 1
Test3
这里发了两次消息,新增的消息是发送setTest的,ARC在setTest的加入了objc_storeStrong。
所以ARC填入后完整代码是:
id temp = [Test newTest];
[self setTest:temp];
objc_release(temp);
- (void)setTest:(Test *test) {
objc_storeStrong(&_test, test);
}
Test4
接下来的4 5 6都采用了createTest,所以不持有返回对象,不需要手动释放。
这里ARC修改后的代码应该是这样的:
// Test
+ (instancetype)createTest {
id temp = [self new];
return objc_autoreleaseReturnValue(temp);
}
// VC
- (void)testForARC {
objc_unsafeClaimAutoreleasedReturnValue([Test createTest]);
}
这里可以看出不仅仅多了一个objc_unsafeClaimAutoreleasedReturnValue
,并且在create中多了objc_autoreleaseReturnValue,这是因为ARC规则,无需手动释放的内部自动autorelease。
objc_autoreleaseReturnValue:
这个函数的作用相当于代替我们手动调用 autorelease
, 创建了一个autorelease
对象。编译器会检测之后的代码, 根据返回的对象是否执行 retain
操作, 来设置全局数据结构中的一个标志位, 来决定是否会执行 autorelease
操作。该标记有两个状态, ReturnAtPlus0
代表执行 autorelease,
以及ReturnAtPlus1
代表不执行 autorelease
。
objc_unsafeClaimAutoreleasedReturnValue:
这个函数的作用是对autorelease
对象不做处理仅仅返回,对非autorelease
对象调用objc_release
函数并返回。所以本情景中它创建时执行了 autorelease
操作了,就不会对其进行 release
操作了。只是返回了对象,在合适的实际autoreleasepool
会对其进行释放的。
Test5
objc_retainAutoreleasedReturnV