前言
伴随着iOS5的发布,在Xcode4.2中加入了一个振奋人心的新特性。ARC,开启这个特性后,帮我们省去了许多内存管理的代码,让我们把更多的精力集中到功能的实现上。虽然ARC如此的完美,但作为iOS Developer,学习MRC,同样的重要。
为什么要学内存管理 ?
- iOS应用程序出现Crash(闪退),90%的原因是因为内存问题
- 在iPhone 6s以前,大多数iPhone的运行内存都是1G,每个应用程序启动后分配的内存空间极其有限,当应用程序运行过程中所占用的内存较大时,便会收到系统给出的内存警告,如果应用程序所占用的内存超过限制时,便会被系统强制关闭,所以我们需要对应用程序进行内存管理,一个好的程序程序也应该尽可能少地占用内存
内存管理的两种方式
- MRC
- Mannul Reference Counting
- 手动引用计数
- ARC
- Automatic Reference Counting
- 自动引用计数
- 从Xcode4.2过后,系统默认就开启了ARC,但我们可以手动选择使用MRC
哪些变量需要做内存管理
- 通常由程序员自己创建的对象(继承于NSObject)需要做内存管理,因为他们存储在堆内存中
- 而基本数据类型,比如int, float,double,char,struct等不需要做内存管理,因为它们存储在栈内存中,栈内存中的变量会自己管理自己。
MRC
- Mannul Reference Counting
- 手动引用计数
- 在iOS5以前,程序员普遍使用MRC方式来管理内存,程序员需要自己添加retain、release和autorelease等内存管理代码来跟踪自己所拥有的对象以明确地管理对象的内存,在需要使用该对象的时候保证该对象一直存在于内存中,在不需要使用该对象的时候保证该对象所占用的内存被系统正常回收
- 为了让系统知道何时需要将某个对象所占用的内存清理掉,系统引入了引用计数器的概念
引用计数器
- 概念
- 系统为每个OC对象内部都分配了4个字节的存储空间存放自己的引用计数器,引用计数器是一个整数,表示“对象被引用的次数”,当对象的引用计数器为0时,对象所占用的内存空间就会被系统自动回收,当对象的引用计数器不为0时,在程序运行过程中所占用的内存会一直存在,直到整个程序退出时由OS自动释放
- 操作应用计数器
- 当使用alloc、new、copy、mutableCopy创建一个新对象时,该新对象的引用计数器为1
- 当给对象发送一条retain消息时,对象的引用计数器+1(方法返回对象本身)
- 当给对象发送一条release消息时对象的引用计数器-1(方法无返回值)
- 当给对象发送一条retainCount消息时,返回对象的当前引用计数器(不要以该数据来判断对象是否被释放)
- 例子
Person *p = [[Person alloc] init]; // 使用alloc创建一个新对象,对象引用计数器 = 1
[p retain]; // 给对象发送一条retain消息,对象引用计数器 + 1 = 2
[p release]; // 给对象发送一条release消息,对象引用计数器 - 1 = 1
[p release]; // 给对象发送一条release消息,对象引用计数器 - 1 = 0,指针所指向的对象的内存被释放
- 注意
- 当引用计数器为0时,会自动调用dealloc方法
dealloc方法
- 当系统回收对象的内存时,系统会自动给该对象发送一条dealloc消息,我们一般会重写dealloc方法,在这里给当前对象所拥有的资源(包括实例变量)发送一条release消息(基本数据类型不用),保证自身所拥有的资源也可以正常释放(因为在使用该资源的时候,采用retain获取了该资源的所有权,在自身释放的同时,也应该放弃对该资源的所有权)
- (void)dealloc
{
NSLog(@"Person dealloc");
// release对象所拥有资源
[_room release];
// 设置为nil可以避免野指针错误(其实可以不设置,只是写了显得有逼格)
_room = nil;
[super dealloc];
}
- 注1: 不要直接调用对象的dealloc方法
- 注2: 重写dealloc方法时,一定要调用[super dealloc]方法,且放在代码的最后
- 注3: 当应用程序被关掉,dealloc方法不一定会被调用,因为由系统OS直接来释放内存比调用dealloc释放内存效率得多
- 注4: 不要在dealloc方法中管理稀缺资源(比如网络连接,文件资源,DOS命令等),因为dealloc并不一定都是立即调用,有可能会延迟调用,也可能根本不会被调用
僵尸对象、野指针与空指针
- 僵尸对象
- 所占用的内存已经被回收的对象,僵尸对象不能再使用
- 野指针
- 指向僵尸对象的指针,给野指针发送消息会报错EXC_BAD_ACCESS错误:访问了一块已经被回收的内存
- 空指针
- 没有指向任何对象的指针(存储的东西是nil,NULL,0),给空指针发送消息不会报错,系统什么也不会做,所以在对象被释放时将指针设置为nil可以避免野指针错误
注: 默认情况下,Xcode是不会监听僵尸对象的,所以需要我们自己手动开启,开启监听僵尸对象步骤为: Edit Scheme ->; Run ->; Diagnostics ->; Objective-C的Enable Zombie Objects打钩,这样便可以在因为僵尸对象报错的时候给出更多错误信息
- 例子
// 引用计数器 = 1
Person *p = [[Person alloc] init];
// 引用计数器 - 1 = 0,指针所指向的对象的内存被释放
[p release];
// 这句给野指针发送消息,会报野指针错误,开启监听僵尸对象会给出错误信息
// -[Person release]: message sent to deallocated instance 0x100206fd0
[p release];
- 如何避免野指针问题
- 如果在第一次给对象发送release消息后,立刻将指针置空,便不会出现野指针错误,因为给空指针发送消息不会报错,系统什么也不会做,所以在对象被释放时将指针设置为nil可以避免野指针错误
自动释放池
自动释放池提供了延迟放弃一个对象的所有权的机制,比如想要在一个方法中返回一个对象,如果先使用release放弃了该对象的所有权,那么return返回的对象便是一个僵尸对象,如果先进行return返回,那么便无法放弃该对象的所有权,导致了内存泄漏
- 创建
- iOS5.0之前创建自动释放池方法(现在也可使用)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// do something...
[pool release];
- iOS5.0之后创建自动释放池方法
@autoreleasepool
{
// do something...
}
- autorelease方法
- 是一种支持引用计数的内存管理方式,只要在自动释放池中给对象发送一条autorelease消息,就会将对象放到自动释放池中,当自动释放池被销毁时,会对池中的所有对象发送一条release消息
- autorelease方法会返回对象本身
- autorelease方法不会修改对象的引用计数器
- autorelease方法可以让开发者不用实时关心什么时候发送release消息
注1: 自动释放池被销毁时,只是给池中所有对象发送一条release消息,不代表对象一定会被释放
注2: 对象在自动释放池中每收到一条autorelease消息,在自动释放池被销毁时,对象都会收到一次release消息
- autorelease方法使用注意事项
- 一定要在自动释放池中调用autorelease方法,才会将对象放入自动释放池中
- 即使在自动释放池内创建对象,只要不调用了autorelease方法,就不会将对象放入自动释放池中
- 即使在自动释放池外创建对象,只要在自动释放池中调用了autorelease方法,就会将对象放入自动释放池中
- 一个程序中可以创建N个自动释放池,且自动释放池可以嵌套,这些自动释放池以栈结构存在(先进后出),当一个对象调用autorelease方法时,会将这个对象放到栈顶的自动释放池中
- autorelease不能精准地释放内存(延迟释放),因为要将池中的所有内容都执行完才会释放自动释放池,所以占用内存比较大的东西还是使用release为宜
ARC
Automatic Reference Counting
自动引用计数
它是iOS4引入的一项新技术(从iOS5开始支持弱引用),其使用与MRC相同的内存管理规则来管理内存,不过编译器会在编译阶段自动地在适当的位置插入retain、release和autorelease等内存管理代码来管理内存(属于编译器特性,不是运行时特性),不再需要程序人员手动管理.官方强烈建议使用ARC方式来管理内存
注: OC中的ARC和Java中的垃圾回收机制不一样,Java中的垃圾回收是系统做的,而OC中的ARC是编译器做的
- MRC和ARC示例
// MRC
@interface Person : NSObject
@property (retain) NSNumber *number;
@end
@implementation Person
- (void)dealloc
{
NSLog(@"Person dealloc");
[_number release];
[super dealloc];
}
@end
Person *person = [[Person alloc] init];
NSNumber *number = [[NSNumber alloc] initWithInt:2];
person.number = number;
[number release];
[person release];
// perosn和number正常被释放
// ARC
@interface Person : NSObject
@property (strong) NSNumber *number;
@end
@implementation Person
- (void)dealloc
{
NSLog(@"Person dealloc");
}
@end
Person *person = [[Person alloc] init];
NSNumber *number = [[NSNumber alloc] initWithInt:2];
person.number = number;
// perosn和number出了作用域正常被释放
-
ARC与MRC的混合开发
- 如果想在ARC项目中使用MRC文件,可以在Build Phases中的Compile Sources中对应文件加入编译标记-fno-objc-arc
- 如果想在MRC项目中使用ARC文件,可以在Build Phases中的Compile Sources中对应文件加入编译标记-fobjc-arc
-
ARC引入的新规则
- 为了使ARC能够正常工作,在ARC中引入了一些区别于当前编译模式的新的规则,如果你违反了这些规则,在编译阶段编译器会给出一个警告
- 不能实现或者调用retain、release、autorelease和retainCount方法,甚至不能使用@selector(retain)、@selector(release)等方式调用
- 不能调用dealloc方法
- 可以实现dealloc方法,用于释放除了实例变量以外的其他资源
- 不需要在这里释放实例变量(实际上也不能在这里给实例变量发送release消息)
- 可以在这里调用[systemClassInstance setDelegate:nil],以便处理不是用ARC编译的systemClass(在MRC下delegate使用assign修饰,如果自身被释放,delegate会变成野指针,所以需要在dealloc中将其置空;在ARC下delegate使用weak修饰,如果自身被释放,delegate会自动置空)
- 不需要调用[super dealloc],编译器会自动调用
不能使用NSAutoreleasePool来创建自动释放池,而是需要使用@autoreleasepool来代替
为了与MRC之间进行互相操作,ARC中不允许给存取器命名为以new开头(即不能声明以new开头的属性),除非为该属性定义一个新的getter名称为了与MRC之间进行互相操作,ARC中不允许给存取器命名为以new开头(即不能声明以new开头的属性),除非为该属性定义一个新的getter名称
// 错误
@property NSString *newTitle;
// 正确
@property (getter=theNewTitle) NSString *newTitle;
ARC引入的新特性
两个属性修饰符: strong和weak
- 在ARC中新增了两个属性修饰符: strong和weak,其中strong是默认修饰符,下面介绍一下这两个属性修饰符与retain和assign的区别
// 下面这句对于strong的示例,与此同义: @property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;
// 下面这句对于weak的示例,与此相似: @property(assign) MyClass *myObject;
// 使用assign修饰的指针所指向的对象如果被释放,该指针会变成野指针;使用weak修饰的指针所指向的对象如果被释放,该指针会变成空指针
@property(weak) MyClass *myObject;
- 针对于ARC中属性修饰符的使用,要进行如下变化
- strong用于OC对象,相当于MRC中的retain
- weak用于OC对象,相当于MRC中的assign
- assign用于基本数据类型,相当于MRC中的assign
注: 其实就是将MRC中的assign分成了两个部分,分别用于修饰OC对象与基本数据类型
四个变量修饰符
在ARC中新增了四个变量修饰符: 双下划线strong、双下划线weak、双下划线unsafe_unretained和双下划线autoreleasing,其中双下划线strong是默认修饰符,下面介绍一下这四个变量修饰符
- 双下划线strong: 强引用,只要有强指针指向该变量,该变量便会一直存在
- 双下划线weak: 弱引用,只要没有强指针指向该变量,该变量便会被置空(即设置为nil)
- 双下划线unsafe_unretained: 不安全的弱引用,只要没有强指针指向该变量,该变量不会被置空(即设置为nil),而会变成野指针
- 双下划线autoreleasing: 用于标示自动释放的变量
- 官方提醒,在为变量添加修饰符时,最正确的方式如下
// 规则
ClassName * qualifier variableName;
// 正确示例
MyClass * __weak myWeakReference;
MyClass * __unsafe_unretained myUnsafeReference;
// 错误示例(虽然错误,但是编译器会默认为正确,官方说法为"forgiven")
__weak MyClass * myWeakReference;
__unsafe_unretained MyClass * myUnsafeReference;
注: 在直接使用__weak修饰变量指向一个刚创建的对象时,需要注意对象刚刚创建出来就会释放的情况
NSString * __weak string = [[NSString alloc] initWithFormat:@"loly"];
// 因为没有强指针指向该对象,该对象会立即被释放
最后
温故而知新。