ARC全称Automatic Reference Counting,自动引用计数内存管理,是苹果在 iOS 5、OS X Lion 引入的新的内存管理技术。ARC是一种编译器功能,它通过LLVM编译器和Runtime协作来进行自动管理内存。
LLVM编译器会在编译时在合适的地方为 OC 对象插入retain、release和autorelease代码来自动管理对象的内存,省去了在MRC手动引用计数下手动插入这些代码的工作,减轻了开发者的工作量,让开发者可以专注于应用程序的代码、对象图以及对象间的关系上。
概述
ARC会分析对象的生存期需求,并在编译时自动插入适当的内存管理方法调用的代码,而不需要你记住何时使用retain、release、autorelease方法。编译器还会为你生成合适的dealloc方法。一般来说,如果你使用ARC,那么只有在需要与使用MRC的代码进行交互操作时,传统的 Cocoa 命名约定才显得重要。
默认情况下,对象属性是strong。
1. 新规则
ARC引入了一些在使用其他编译器模式时不存在的新规则。这些规则旨在提供完全可靠的内存管理模型。有时候,它们直接地带来了最好的实践体验,也有时候它们简化了代码,甚至在你丝毫没有关注内存管理问题的时候帮你解决了问题。在ARC下必须遵守以下规则,如果违反这些规则,就会编译错误。
- 不能使用 retain / release / retainCount / autorelease
- 不能使用 NSAllocateObject / NSDeallocateObject
- 须遵守内存管理的方法命名规则
- 不能显式调用 dealloc
- 使用 @autoreleasepool 块替代 NSAutoreleasePool
- 不能使用区域(NSZone)
- 对象型变量不能作为 C 语言结构体(struct / union)的成员
- 显式转换 “id” 和 “void *” —— 桥接
不能使用 retain / release / retainCount / autorelease
在ARC下,禁止开发者手动调用这些方法,也禁止使用@selector(retain),@selector(release) 等,否则编译不通过。但你仍然可以对 Core Foundation 对象使用CFRetain、CFRelease等相关函数(请参阅《Managing Toll-Free Bridging》章节)。
不能使用 NSAllocateObject / NSDeallocateObject
在ARC下,禁止开发者手动调用这些函数,否则编译不通过。 你可以使用alloc创建对象,而Runtime会负责dealloc对象。
须遵守内存管理的方法命名规则
在MRC下,通过 alloc / new / copy / mutableCopy 方法创建对象会直接持有对象,我们定义一个 “创建并持有对象” 的方法也必须以 alloc / new / copy / mutableCopy 开头命名,并且必须返回给调用方所应当持有的对象。如果在ARC下需要与使用MRC的代码进行交互,则也应该遵守这些规则。
为了允许与MRC代码进行交互操作,ARC对方法命名施加了约束: 访问器方法的方法名不能以new开头。这意味着你不能声明一个名称以new开头的属性,除非你指定一个不同的getterName
// Won't work:
@property NSString *newTitle;
// Works:
@property (getter = theNewTitle) NSString *newTitle;
不能显式调用 dealloc
无论在MRC还是ARC下,当对象引用计数为 0,系统就会自动调用dealloc方法。大多数情况下,我们会在dealloc方法中移除通知或观察者对象等。
在MRC下,我们可以手动调用dealloc。但在ARC下,这是禁止的,否则编译不通过。
在MRC下,我们实现dealloc,必须在实现末尾调用[super dealloc]。
使用 @autoreleasepool 块替代 NSAutoreleasePool
在ARC下,自动释放池应使用@autoreleasepool,禁止使用NSAutoreleasePool,否则编译错误。
关于@autoreleasepool
的原理,可以参阅《iOS - 聊聊 autorelease 和 @autoreleasepool》。
不能使用区域(NSZone)
对于现在的运行时系统(编译器宏 __ OBJC2 __ 被设定的环境),不管是MRC还是ARC下,区域(NSZone)都已单纯地被忽略。
NSZone 是为防止内存碎片化而引入的结构。...
摘自《Objective-C 高级编程:iOS 与 OS X 多线程和内存管理》
对象型变量不能作为 C 语言结构体(struct / union)的成员
C 语言的结构体(struct / union)成员中,如果存在 Objective-C 对象型变量,便会引起编译错误。
备注: Xcode10 开始支持在 ARC 模式下在 C Struct 里面引用 Objective-C 对象。之前可以用 Objective-C++。
struct Data {
NSMutableArray *mArray;
};
// error:ARC forbids Objective-C objs in struct or unions NSMutableArray *mArray;
虽然是 LLVM 编译器 3.0,但不论怎样,C 语言的规约上没有方法来管理结构体成员的生存周期。因为ARC把内存管理的工作分配给编译器,所以编译器必须能够知道并管理对象的生存周期。例如 C 语言的自动变量(局部变量)可使用该变量的作用域管理对象。但是对于 C 语言的结构体成员来说,这在标准上就是不可实现的。因此,必须要在结构体释放之前将结构体中的对象类型的成员释放掉,但是编译器并不能可靠地做到这一点,所以对象型变量不能作为 C 语言结构体的成员。
这个问题有以下三种解决方案:
① 使用 Objective-C 对象替代结构体。这是最好的解决方案。如果你还是坚持使用结构体,并把对象型变量加入到结构体成员中,可以使用以下两种方案:
② 将 Objective-C 对象通过Toll-Free Bridging强制转换为void *类型,请参阅《Managing Toll-Free Bridging》章节。
③ 对 Objective-C 对象附加__unsafe_unretained修饰符。
struct Data {
NSMutableArray __unsafe_unretained *mArray;
};
附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。如果管理时不注意赋值对象的所有者,便有可能遭遇内存泄漏或者程序崩溃。这点在使用时应多加注意。
struct x { NSString * __unsafe_unretained S; int X; }
__unsafe_unretained指针在对象被销毁后是不安全的,但它对诸如字符串常量之类的从一开始就确定永久存活的对象非常有用。
显式转换 “id” 和 “void *” —— 桥接
在MRC下,我们可以直接在 id 和 void * 变量之间进行强制转换。
//MRC下
id obj = [[NSObject alloc] init];
void *p = obj;
id o = p;
[o release];
但在ARC
下,这样会引起编译报错:在Objective-C
指针类型id
和C
指针类型void *
之间进行转换需要使用Toll-Free Bridging
,请参阅 Managing Toll-Free Bridging 章节。
//ARC下
id obj = [[NSObject alloc] init];
void *p = obj; // error:Implicit conversion of Objective-C pointer type 'id' to C pointer type 'void *' requires a bridged cast
id o = p; // error:Implicit conversion of C pointer type 'void *' to Objective-C pointer type 'id' requires a bridged cast
[o release]; // error:'release' is unavailable: not available in automatic reference counting mode
2. 所有权修饰符
ARC为对象引入了几个新的生命周期修饰符(我们称为 “所有权修饰符”)以及弱引用功能。弱引用weak不会延长它指向的对象的生命周期,并且该对象没有强引用(即dealloc)时自动置为nil。
你应该利用这些修饰符来管理程序中的对象图。特别是,ARC
不能防止强引用循环(以前称为Retain Cycles
,请参阅《从 MRC 说起 —— 使用弱引用来避免 Retain Cycles》章节)。明智地使用弱引用weak
将有助于确保你不会创建循环引用。
属性关键字
ARC中引入了新的属性关键字strong和weak
// 以下声明同:@property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;
// 以下声明类似于:@property(assign)MyClass *myObject;
// 不同的是,如果 MyClass 实例被释放,属性值赋值为 nil,而不像 assign 一样产生悬垂指针。
@property(weak) MyClass *myObject;
strong和weak属性关键字分别对应__strong和__weak所有权修饰符。在ARC下,strong是对象类型的属性的默认关键字。
在ARC中,对象类型的变量都附有所有权修饰符,总共有以下 4 种。
__strong
__weak
__unsafe_unretained
__autoreleasing
- __strong是默认修饰符。只要有强指针指向对象,对象就会保持存活。
__strong修饰符为强引用,会持有对象,使其引用计数 +1。该修饰符是对象类型变量的默认修饰符。如果我们没有明确指定对象类型变量的所有权修饰符,其默认就为__strong修饰符。
- __weak指定一个不使引用对象保持存活的引用。当一个对象没有强引用时,弱引用weak会自动置为nil。
如果单单靠__strong完成内存管理,那必然会发生循环引用的情况造成内存泄漏,这时候__weak就出来解决问题了。 __weak修饰符为弱引用,不会持有对象,对象的引用计数不会增加。__weak可以用来防止循环引用。
- __unsafe_unretained指定一个不使引用对象保持存活的引用,当一个对象没有强引用时,它不会置为nil。如果它引用的对象被销毁,就会产生悬垂指针。
__unsafe_unretained修饰符的特点正如其名所示,不安全且不会持有对象。
- __autoreleasing用于表示通过引用(id *)传入,并在返回时(autorelease)自动释放的参数。
避免循环引用
- __weak和__unsafe_unretained 修饰符解决循环引用
解决 “循环引用” 问题就是采用 “断环” 的方式,让其中一方持有另一方的弱引用。同MRC,父对象对它的子对象持有强引用,而子对象对父对象持有弱引用。 - delegate 避免循环引用
delegate避免循环引用,就是在委托方声明delegate属性时,使用weak关键字
@property (nonatomic, weak) id<protocolName> delegate;
- block 避免循环引用
更多关于block
的内容,可以参阅《OC - Block 详解》。
为什么 block 会产生循环引用?
① 相互循环引用: 如果当前block对当前对象的某一成员变量进行捕获的话,可能会对它产生强引用。根据block的变量捕获机制,如果block被拷贝到堆上,且捕获的是对象类型的auto变量,则会连同其所有权修饰符一起捕获,所以如果对象是__strong修饰,则block会对它产生强引用(如果block在栈上就不会强引用)。而当前block可能又由于当前对象对其有一个强引用,就产生了相互循环引用的问题;
② 大环引用: 我们如果使用__block的话,在ARC下可能会产生循环引用(MRC则不会)。由于__block修饰符会将变量包装成一个对象,如果block被拷贝到堆上,则会直接对__block变量产生强引用,而__block如果修饰的是对象的话,会根据对象的所有权修饰符做出相应的操作,形成强引用或者弱引用。如果对象是__strong修饰(如__block id x),则__block变量对它产生强引用(在MRC下则不会),如果这时候该对象是对block持有强引用的话,就产生了大环引用的问题。在ARC下可以通过断环的方式去解除循环引用,可以在block中将指针置为nil(MRC不会循环引用,则不用解决)。但是有一个弊端,如果该block一直得不到调用,循环引用就一直存在。
ARC下解决方式
- 用__weak或者__unsafe_unretained解决:
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
};
注意:__unsafe_unretained会产生悬垂指针,建议使用weak。
__unsafe_unretained id uuSelf = self;
self.block = ^{
NSLog(@"%p",uuSelf);
};
对于 non-trivial cycles,我们需要这样做:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if(!strongSelf) return;
NSLog(@"%p",weakSelf);
};
- 用__block解决(必须要调用block)
缺点:必须要调用block,而且block里要将指针置为nil。如果一直不调用block,对象就会一直保存在内存中,造成内存泄漏。
__block id blockSelf = self;
self.block = ^{
NSLog(@"%p",blockSelf);
blockSelf = nil;
};
self.block();
3.
在ARC下,我们可以使用_objc_rootRetainCount函数查看对象的引用计数。
uintptr_t _objc_rootRetainCount(id obj);