【iOS夯实】内存管理之ARC的基本使用
什么是 ARC
ARC (Automatic Reference Counting) 是2011年 WWDC 中,苹果为了解决由于内存管理引起的 Crash 而提出的解决方案。
简单来说,ARC提供是一个编译器的特性,帮助我们在编译的时候自动插入管理引用计数的代码,帮助我们完成之前 MRC 需要完成的工作。ARC的本质仍然是通过引用计数来管理内存。
ARC 的使用准则
- 不可以调用
dealloc
, 不能调用或实现跟引用计数相关的方法(retain/release/autorelease/retainCount
等),否则产生编译错误。 - 不可以使用
NSAllocateObject
和NSDeallocateObject
。 - 在 C 结构体里不可以使用
Objective C
对象。 - 不能显式转换
id
和void *
。
id
指的是Objective C
对象,void *
指的是 C 指针(CGColorRef
),id
和void *
之间赋值要添加__bridge
系列关键字。 - 不可以使用
NSAutoreleasePool
, 用@autoreleasepool
代替。 - 不可以使用区域 (
NSZone
)。 - 不可以使用以
new
开头的属性名称。若使用会有以下的编译错误”Property’s synthesized getter follows Cocoa naming convention for returning ‘owned’ objects”
ARC 所有权修饰符
Objective-C 编程中为了处理对象,可以将变量类型定义为 id 类型或者各种对象类型。
对象类型就是指向 NSObject
这样的 Objective-C 类的指针,比如 NSObject *
。
id 类型用于隐藏对象类型的类名部分,类似 C 语言的 Void *
。
ARC 有效时,id 类型和对象类型和 C 语言其他类型不同,其类型上必须附加所有权修饰符。
所有权修饰符有 4 种
-
__strong
修饰符 -
__weak
修饰符 -
__unsafe_unretained
修饰符 -
__autoreleasing
修饰符
4种修饰符均可以保证附有这些修饰符的自动变量初始化为 nil
id __strong object1;
id __weak object2;
相当于
id __strong object1 = nil;
id __weak object2 = nil;
__strong 修饰符
__strong
修饰符是 id
类型和对象类型默认的所有权修饰符,也就是说 id
和对象类型在没有明确指定所有权修饰符的时候,默认为__strong
。
// 两行代码效果相同
id object = [[NSObject alloc] init];
id __strong object = [[NSObject alloc] init];
当 ARC 无效时
{
id __strong object = [[NSObject alloc] init];
}
可记述为如下代码
{
// ARC 无效时
id __strong object = [[NSObject alloc] init];
[object release];
}
为了释放生成并持有的对象,增加了调用 release
方法的代码。
如上面代码所示, __strong
修饰符表示对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
关注一下代码中关于对象的所有者的部分
{
id __strong object = [[NSObject alloc] init];
}
该对象的所有者如下
{
// 自己生成并持有对象。
id __strong object = [[NSObject alloc] init];
// 变量 object 为强引用,所以自己持有对象。
}
// 变量 object 超出其作用域,强引用失效,自动释放自己持有的对象。
// 对象的所有者不存在,因此废弃该对象。
取得非自己生成并持有的对象时
{
id __strong object = [NSMutableArray array];
}
在 NSMutableArray 类的 array 类方法的代码中取得非自己生成并持有的对象
{
// 取得非自己生成并持有的对象时。
id __strong object = [NSMutableArray array];
// 变量 object 为强引用,所以自己持有对象。
}
// 变量 object 超出其作用域,强引用失效,自动释放自己持有的对象。
// 对象的所有者不存在,因此废弃该对象。
附有 __strong
修饰符的变量之间可以相互赋值
id __strong object1 = [[NSObject alloc] init]; // 对象1
// object1 持有对象1的强引用
id __strong object2 = [[NSObject alloc] init]; // 对象2
// object2 持有对象2的强引用
id __strong object3 = nil;
// object3 不持有任何对象
object1 = object2;
// object1 持有由 object2 赋值的对象2的强引用
// 由于 object1 被赋值,持有的对对象1的强引用失效;对象1的所有者不存在,所以废弃对象1。
object3 = object1;
// object3 持有由 object1 赋值的对象2的强引用。
// 此时,持有对象2的强引用的变量有 object1,object2 和 object3。
object1 = nil;
object2 = nil;
object3 = nil;
// nil被赋予 object1,object2 和 object3,对对象2的强引用失效。
// 对象2的所有者不存在,所以废弃对象2。
__weak 修饰符
带有 __strong
修饰符的成员变量在持有对象时,很容易发生循环引用,从而容易引起内存泄漏(内存泄漏就是应当废弃的对象在超出其生存周期后继续存在)。

使用 __weak
修饰符可以避免循环引用,__weak
修饰符和 __strong
相反,提供弱引用。弱引用不持有对象实例。
以下是带有 __strong 修饰符的成员变量在持有对象时发生循环引用的例子
@interface Test : NSObject
{
id __strong obj_;
}
- (void)setObject:(id __strong) obj;
@end
@implementation Test
- (id)init
{
self = [super init];
return self;
}
- (void)setObject:(id __strong)obj
{
obj_ = obj;
}
@end
以下为循环引用
{
id test1 = [[Test alloc] init];//对象1
// test1 持有 Test 对象1的强引用。
id test2 = [[Test alloc] init];//对象2
// test2 持有 Test 对象2的强引用。
[test1 setObject:test2];
// Test 对象1的 obj_ 成员变量持有 Test 对象2的强引用。
// 持有 Test 对象2的强引用的变量为 Test 对象1的 obj_ 和 test2。
[test2 setObject:test1];
// Test 对象2的 obj_ 成员变量持有 Test 对象A的强引用。
// 持有 Test 对象1的强引用的变量为 Test 对象2的 obj_ 和 test1。
}
// test1 变量超出其作用域,强引用失效,自动释放 Test 对象1。
// test2 变量超出其作用域,强引用失效,自动释放 Test 对象2。
// 此时持有 Test 对象1的强引用的变量为 Test 对象2的obj_。
// 此时持有 Test 对象2的强引用的变量为 Test 对象1的obj_。
// 发生内存泄漏。
类成员变量的循环引用

对自身的强引用
id test [[Test alloc] init];
[test setObject:test];

解决循环引用的方案
由于弱引用不持有对象,所以在超出其变量作用域时,对象即被释放。
所以将可能发生循环引用的类成员变量改为带 __weak
修饰符的成员变量,可以避免循环引用现象。
@interface Test : NSObject
{
id __weak obj_;
}
- (void)setObject:(id __strong)obj;
@end
使用 __weak 修饰符注意事项
id __weak obj = [[NSObject alloc] init];
编译运行会出现以下警告

上面的代码将自己生成并持有的对象赋值给带有
__weak
修饰符的变量 obj,即变量 obj 持有对持有对象的弱引用。为了不以自己持有的姿态来保存自己生成并持有的对象,生成的对象会立即被释放。这是编译器的警告,将对象赋值给带 __strong
修饰符的变量之后再赋值给带 __weak
修饰符的变量,就不会出现警告。
{
id __strong object1 = [[NSObject alloc] init];
id __weak object1 = object1;
}
__unsafe_unretained 修饰符
__unsafe_unretained
修饰符是不安全的所有权修饰符。
带有 __unsafe_unretained
修饰符的变量不属于编译器的内存管理对象。
id __unsafe_unretained object = [[NSObject alloc] init];
编译运行会出现以下警告

带 __unsafe_unretained
修饰符的变量和带 __weak
变量一样,为了不以自己持有的姿态来保存自己生成并持有的对象,生成的对象会立即被释放。
使用 __unsafe_unretained 修饰符注意事项
使用 __unsafe_unretained
修饰符时,赋值给带 __strong
的变量时要确保被赋值的对象确实存在。
__autoreleasing 修饰符
ARC有效时,需要通过将对象赋值给带 __autoreleasing
修饰符的变量来替代调用 autorelease
方法。对象赋值给带有 __autoreleasing
修饰符的变量等价于在 ARC 无效时调用对象的 autorelease
方法,即对象被注册到 autoreleasepool
。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[obj autorelease];
[pool drain]
等价于
@autoreleasepool{
id __autoreleasing obj2;
obj2 = obj;
}
作为 alloc
、new
、copy
、mutableCopy
方返回值取得的对象是自己生成并持有的,其他情况下便是取得非自己生成并持有的对象。所以使用带有 __autoreleasing
修饰符的变量作为对象取得参数,与除 alloc
、new
、copy
、mutableCopy
外其他方法的返回值取得对象完全一致,都会注册到 autoreleasepool
,并取得非自己生成并持有的对象。(编译器会检查方法名是否以 alloc
、new
、copy
、mutableCopy
开始,如果不是就自动将返回值得对象注册到 autoreleasepool
)
@autoreleasepool{
// 取得非自己生成并持有的对象
id __strong obj = [NSMutableArray array];
// 变量 obj 为强引用,自己持有对象。
// 该对象由编译器判断其方法名后自动注册到 autoreleasepool
}
// 变量 obj 超出其作用域,强引用失效,自动释放对象
// @autoreleasepool 块的结束,注册到 autoreleasepool 中的所有对象被自动释放
访问带有 __weak
修饰符的变量必须访问注册到 autoreleasepool
的对象,因为 __weak
修饰符只持有对象的弱引用,而在访问对象的工程中,对象有可能被废弃。如果要把访问的变量注册到 autoreleasepool
中,那么在 @autoreleasepool
块结束前均能保证该对象存在。
id __weak obj2 = obj1;
等价于
id __weak obj2 = obj1;
id __autoreleasing temp = obj2;
最后
本人为iOS开发新手一枚,写的不好的或写错的地方,希望各位大侠能帮忙指正。
各位大侠,如果觉得对自己有点用的,欢迎点个赞,也欢迎大家关注我( Github / 简书 / 微博 / Instagram / 知乎)
谢谢观看此文。