Objective-C自动引用计数(ARC)与内存管理

Memory Management
阅读完《Objective-C高级编程:iOS与OS X多线程和内存管理》,对 Objective-C ARC 与内存管理进行整理总结。

概要

举例说明引用计数机制:
Reference Count

内存管理的思考方式

客观正确的思考方式非关注在计数上,而是:

  • 自己生成的对象,自己所持有
  • 非自己生成的对象,自己也能持有
  • 不再需要自己持有的对象时释放
  • 非自己持有的对象无法释放
对象操作 Objective-C方法
生成并持有对象 alloc/new/copy/mutableCopy等
持有对象 retain
释放对象 release
废弃对象 dealloc

自己生成的对象,自己所持有、释放

1
2
3
4
5
//自己生成,并持有
id obj = [[NSObject alloc] init];
//自己持有
[obj release];
//释放对象

new、copy、mutableCopy等方法也同样适用

非自己生成的对象,自己也能持有、释放

1
2
3
4
5
6
7
//非自己生成对象
id obj = [[NSMutableArray array];
//自己不持有对象
[obj retain];
//自己持有对象
[obj release];
//释放对象

生成对象的方法

非自己生成并持有:

1
2
3
4
5
6
7
8
9
- (id)allocObject {
//自己生成并持有
id obj= [[NSObject alloc] init];
//自己持有
return obj;
}
//取得非自己生成并持有的对象
id obj1 = [obj0 allocObject];
//自己持有对象

类似[NSMutableArray array],取得对象存在但不持有:

1
2
3
4
5
6
7
8
- (id)object {
//自己生成并持有
id obj= [[NSObject alloc] init];
//自己持有
[obj autorelease];
//取得存在但不持有
return obj;
}

非自己持有的对象无法释放

如释放已释放的对象,或者释放[NSMutableArray array]自己不持有的对象,会造成崩溃。

autorelease

对象超出作用域时,release将自动被调用

ARC规则

Xcode配置

Xcode默认所有文件ARC有效,如需配置某些文件 ARC 无效,可以配置增加 -fno-objc-arc:
ARC Setting

所有权修饰符

附有修饰符的变量自动初始化为nil(__unsafe_unretained除外)。

__strong

强引用,id类型和对象类型默认修饰符,不必再次键入 retain 或者 release。

__weak

__strong修饰符无法解决“循环引用”问题,循环引用有可能导致内存泄漏。

__weak修饰符不持有该对象,如下代码编译时会有警告:

1
2
id __weak obj = [[NSObject alloc] init];
//obj为若引用,生成对象会立即释放。

若先赋值给强引用则不会有问题,且 obj0 释放后,obj1 将自动赋值为nil:

1
2
id __strong obj0 = [[NSObject alloc] init];
id __weak obj1 = obj0;

__unsafe_unretained

不适于编译器的内存管理对象。与 __weak 不同得是,对象若是否后不会自动赋值为 nil,所以使用时必须确保对象存在。

__autoreleasing

ARC 有效时不能使用 autorelease 方法以及 NSAutoreleasePool 类,使用 “@autoreleasepool” 代码块来代替 NSAutoreleasePool,使用 __autoreleasing 代替调用 autorelease 方法。
编译器会自动对象注册到 autoreleasepool 或自动添加 release,所以显式调用 __autoreleasing 非常罕见。
id的指针或者对象的指针会默认附加上 __autoreleasing 修饰符,作为 alloc/new/copy/mutableCopy 方法返回值取得的对象是自己生成并持有的,其他情况便是取得非自己生成并持有的对象,会自动注册到autoreleasepool,道理是一样的。

1
- (BOOL) save: (NSError **) error;

等同于

1
- (BOOL) save: (NSError * __autoreleasing *) error;

赋值给对象指针时,修饰符必须一致:

1
2
NSError *error = nil;
NSError **pError = &error;

以上代码编译器会产生告警,必须加上 __strong 修饰符:

1
2
NSError *error = nil;
NSError * __strong *pError = &error;

规则

  • 不能使用 retain/release/retianCount/autorelease
  • 不能使用 NSAllocateObject/NSDeallocateObject
  • 须遵守内存管理的方法命名规则(alloc/new/copy/mutableCopy/init
  • 不要显式调用 dealloc
  • 使用 @autoreleasepool 代替 NSAutoreleasePool
  • 不使用 NSZone
  • 对象不能作为C结构体(struct/union)成员
  • 显示转换“id”和“void*”(__bridge、__bridge_retained、__bridge_transfer)

__bridge_retained

1
2
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void*)obj;

ARC 无效时的等效代码:

1
2
3
4
//ARC无效
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];

__bridge_transfer

1
2
id obj = [[NSObject alloc] init];
void *p = (__bridge_transfer void*)obj;

ARC 无效时的等效代码:

1
2
3
4
5
//ARC无效
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];
[obj release];

属性

属性声明的属性 所有权修饰符
assign __unsafe_unretained
copy __strong(赋值被复制对象)
retain __strong
strong __strong
unsafe_unretained __unsafe_unretained
weak __weak

assign 一般用来修饰基本的数据类型,包括基础数据类型(NSInteger,CGFloat)和C数据类型(int, float, double, char, 等等)。因为基础数据类型会被分配到栈上,而栈会由系统自动处理,不会造成野指针。

数组

主要关注动态数组一些特性,malloc 分配的空间需要 memset 初始化、free 释放,释放前需要将数组元素赋值为 nil

1
2
NSObject * __strong *array = nil;
//修饰符只保存对象初始化为nil,不保证对象的指针初始化为nil
1
2
3
4
//需要手动赋值为nil
for (int i = 0; i < count; i++)
array[i] = nil;
free(array);

ARC实现

可将代码源文件进行汇编输出后,就能大概知道程序是如何工作的。

__strong

1
id __strong obj = [[NSObject alloc] init];
1
2
3
4
//转换为编译器的模拟代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init))
objc_storeStrong

alloc/new/copy/mutableCopy以外的情况:

1
id __strong obj = [NSMutableArray array];
1
2
3
4
//转换为编译器的模拟代码
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleaseReturnValue(obj)
objc_storeStrong

objc_retainAutoreleaseReturnValue 用于自己持有,与之相对的是 objc_autoreleaseReturnValue,将返回值注册到 autoreleasepool。用于以下实现:

1
2
3
+ (id) array {
return [[NSMutableArray alloc] init];
}
1
2
3
4
5
6
//转换为编译器的模拟代码
+ (id) array {
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init))
return objc_autoreleaseReturnValue(obj)
}

通过 objc_autoreleaseReturnValue 和 objc_retainAutoreleaseReturnValue 进行优化,返回值可不注册到 autoreleasepool:
Ignore Autoreleasepool

llvm上 objc_storeStrong 源代码:

1
2
3
4
5
6
void objc_storeStrong(id *object, id value) {
id oldValue = *object;
value = [value retain];
*object = value;
[oldValue release];
}

__weak

obj 不能持有生成的对象,最终 obj 会是 nil:

1
id __weak obj = [[NSMutableArray alloc] init];
1
2
3
4
5
6
7
//转换为编译器的模拟代码
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_initWeak(&obj,tmp);
objc_release(tmp);
objc_destroyWeak(&obj);

若使用了 __weak 修饰的变量,将会调动 objc_loadWeakRetained 取出该变量并 retained,并加入到 autoreleasepool 当中。每使用一次该变量都要执行该动作一次。所以如果要多次使用该变量,最好的办法是在暂时赋值给 __strong 修饰符的变量后再使用。

1
2
3
4
5
- (void)testMethod{
id obj = [[NSObject alloc] init];
id __weak obj1 = obj;
NSLog(@"%@",obj1);
}
1
2
3
4
5
6
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1)
objc_autorelease(tmp);
NSLog(@"%@",tmp);
objc_destroyWeak(&obj1);

__autoreleasing

编译器会自动添加 objc_autorelese() 调用

Cotin Yang wechat
欢迎订阅我的微信公众号 CotinDev
小小地鼓励一下吧~😘