iOS成长之路基础篇 – (4) OC 内存管理MRC和ARC
最近一直在坚持写博客,这些学习笔记主要是自己以前学习OC的过程中记下的笔记,现在整理的过程中就想,把这个写成博客。大牛同志写博客一年好几百篇,我的几年了才30篇不到,心里也鄙视了自己一把。最近却写的停不下来了,准备把这个系列的博客写完,一般两天基本出一篇,虽然比较基础,但是一篇下来大概也是需要两个小时左右整理。现在才知道写文章的人有多么辛苦。后面也希望自己能一直坚持写下去。
今天我总结的是Objective-C的手动内存管理和现在Apple已经全面支持的自动引用计数(自动内存管理)。好,下面我们进入正题。
准备工作
刚才说到,现在的iOS项目已经全面支持ARC,而且,Apple一向的强制原则就是,我推荐的,你就只能按着我的来。所以我们现在在Xcode7上建立的新项目,已经看不到原来的可以勾选的ARC选项,换句话说就是,我们现在只能开发ARC的项目,像Apple的新语言Swift根本就不支持MRC,那么是不是说我们已经彻底不能看到MRC的项目了?答案是NO。那么如何建立一个MRC项目?
首先,我们按着一般建立项目的步骤建立一个新项目,然后查看项目是否为ARC项目:
TARGETS—项目名称——Build Settings—-Objective-C A.. R.. C..:YES
如果我们将YES修改为NO,那么项目就会变成MRC项目,这时候我们就要自己管理引用计数了。
还有就是,我们以前的项目可能是MRC,现在,MRC已经一去不复返,我们想将我们的项目转换成ARC项目,这个怎么办?难道一个文件一个文件修改?答案也是NO。Xcode提供一键将非ARC项目改为ARC项目:
Xcode中Edit—>Refactor—->Convert to Objective-C ARC…
那么,如果我们需要在我们的项目中引入第三方框架,而我们的项目是ARC,第三方框架因为太老而使用的MRC,这时候我们应该怎么办?答案是,Xcode提供对项目中的不同文件使用不同的编译策略。就是我们常说的MRC和ARC混合编程。这里使用到一个编译指令。项目中单文件ARC或者MRC设置位置:
TARGETS—项目名称——Build Phases—-Compile Sources:
对ARC项目不需要ARC的文件设置参数:-fno-objc-arc,如下图(图片来自网络);
对非ARC项目需要ARC的文件设置参数:-fobjc-arc。如下图(图片来自网络):
接下来,我们首先总结一下手动引用计数(MRC)的知识。
MRC内存管理 {.p1}
我们说的引用计数管理,其实就是对对象的内存管理。那么我们的内存管理的对象是哪些呢?任何继承了NSObject的对象。在OC中,每一个OC对象都有自己的引用计数器,是一个整数,对象被引用的次数。存在于对象的内部,占四个字节。
当使用alloc,new和copy创建一个新对象时,新对象的引用计数就默认为1。当引用计数为0,就回收内存,对象被销毁。
- 给对象发送一条retain消息,引用计数+1。
- 给对象发送一条release消息,引用计数-1。
- 可以通过retainCount方法获取引用计数次数。
当对象引用计数为0时,对象被销毁时,系统发送一条dealloc消息。
一般开发中我们将重写dealloc方法,释放相关资源。同时一旦调用了dealloc方法,就必须在最后调用[super dealloc];
注意:
release为0的对象称为僵尸对象,指向僵尸对象的指针成为野指针。
一个对象引用计数为0后不能再调用release方法。会出现野指针错误(EXC_BAD_ACCESS)。OC中没有空指针错误,一个空指针调用任何方法,都会被忽略,不会出错。
retain方法有返回值,返回其对象本身。
在OC中,局部变量和动态产生的对象在内存中是这样的。局部对象存储在栈中,动态产生的对象,首先在堆中申请内存,然后初始化。蓝色的线,相当于指针指向OC对象。此时的堆对象中会有一个引用计数器,它的数值为1。
下面是一段MRC代码:
#import "People.h" int main() { People *p = [[People alloc] init]; //假设有一个People对象,创建对象, //引用计数+1,变为1 [p release]; //在使用完p对象之后要release一次,引用计数-1,变为0 return 0; }
- MRC内存原则
1>需要引用时,+1;不需要引用了,-1;
2>谁创建,谁release;
3>谁retain,谁release;
4>只要调用了alloc,必须有release(autorelease)
5>成员变量的set方法中需要注意的:基本数据不用管,对象数据需要注意。
//虽然@property可以省略setter和getter方法,但是在MRC中,成员变量如果是对象
//我们应该在setter方法中将原来的对象进行一次release
//将新赋值的对象进行一次retain
- (void)setBook: (Book)book
{
if(_book != book) //如果原来的_book和新赋值的不是同一对象,就需要更新引用计数
{
[_book release]; //如果第一次赋值,_book为nil,此句代码将没有人和执行效果
_book = [_book retain];//新的对象计数+1
}
}
上面的代码如果没有学习过OC的可能看不懂,建议在看书系统学习一下。
当然,上面的setter方法中,成员变量引用计数+1,那么我们是不是在释放People对象时,将_book变量也销毁了,此时应该将_book所指的对象引用计数-1。那么在dealloc方法中需要注意对成员对象计数-1。
- (void)dealloc
{
[_book release];
[super release];
}
- @property的参数
上面我们讲到@property可以将基本数据类型的成员变量的getter和setter方法生成,我们不需要人工干预,但是对于对象类型的成员变量,我们必须自己写setter方法,而且对于一个对象类型的变量,代码几乎一样,此时,Apple给@property有添加了一些编译器特性。就是@property的参数。
@property(retain) Book *book; //retain:生成set代码中的release旧值,retain新值
//不能代替dealloc方法,还是需要在dealloc中release对象
其实@property的参数还有很多,主要分为四类:
1、内存管理
- retain : release旧值,retain新值
- assign :直接复制(缺省值,适用于非OC对象类型)
- copy : release旧值,copy新值
2、读写属性,是否要生成set方法
- readaonly :只读,只生成getter的声明和实现
- readwrite :可读可写,生成setter和getter的生命和实现(缺省值)
3、多线程管理
- nonatomic :性能高,不加锁,线程不安全(建议使用,开发常写)
- atomic:性能低,加锁,线程安全(缺省值)
4、setter和getter方法的名称
- @property (setter = setXxx: ,getter = xxx) int weight;
- 一般使用在BOOL类型的成员变量 getter = isXxx
四种参数可以组合使用,只要不是冲突的参数,就可以组合,一般我们开发中的组合为:
// 对象类型
@property(nonatomic, retain) Book *book;
// 基本类型
@property(nonatomic, assign) int age;
// 如果设置读写属性,也可以加上
@property(nonatomic, assign, readonly) double weight;
- @class
如果A文件头文件包含了B文件的头文件,同时B文件有包含了A文件的头文件,此时就会出现无法编译的情况,两个头文件循环导入,最后进入死循环。此时,我们就要用到@class关键字。
@class Car;
仅仅是告诉编译器这是一个类。能解决循环引用时不能import的问题。
一般的开发规范:
- 一般情况下,在.h文件中我们使用@class Xxx;
- 在.m文件中import “Car.h”,提高编译效率。
- autorelease
autorelease关键字,字面意思好像是自动释放,其实它不是自动释放,它的正确理解是:半自动的。就是在自动释放池销毁的时候release一次。
autorelease准确的说是一个函数,返回对象本身id。调用autorelease方法后,将对象放入自动释放池,对象计数不变,当自动释放池销毁时,池中的对象会自动调用release一次。
- 自动释放池
ios程序运行过程中会创建无数多的池子,这些池子都是栈这种数据结构存在(后进先出),当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池。
自动释放池有两种创建方式。
// ios5以前的方式:
NSAutoreleasePool *pool = [NSAutoreleasePool alloc] init];
//中间是自动释放池代码
[pool release];
//下面的方式是ios5.0以后的方式
@autoreleasepool {
//创建自动释放池
//使用方式:
Person *p = [[[Person alloc] init] autorelease];
} //销毁自动释放池,池中所有对象进行release一次。
自动释放池的一些特点:
- 优点:可以延迟对象释放时间,不用关心对象释放时间。
- 缺点:不能准确控制对象释放时间,占用内存较大的对象不要使用autorelease。
注意:
1.自动释放池在内存中的存放数据结构是栈,后进先出。同时自动释放池可以嵌套使用。
2.不能在调用autorelease之后再次调用release,其实autorelease就是在销毁自动释放池时,池中对象进行release一次。
3.不能多次调用autorelease方法,每一个自动释放池对应一个autorelease。
4.一般情况我们自己创建的对象放入栈顶的池子。
- 开发中的技巧
- 调用系统中的对象,系统的对象实例化中没有alloc,new,copy等关键字,说明返回的对象都是autorelease,不需要关心它的内存管理。
- 可以在模型类中设计一些类方法,返回一个带有autorelease方法的对象。
注意:在设计自己的带有autorelease方法的对象的类方法时,方法中的类名使用self代替直接写类名。
ARC内存管理 {.p1}
ARC属于编译器特性。自动生成引用计数的代码,并插入到特定位置,不需要我们关心其中的细节,大大提高程序员的工作效率。那么它是如何判断的呢?
ARC的判断准则:只要没有强指针指向对象,就会释放对象。
- 指针分为两类分类:
A:强指针:默认情况下,所有的指针都是强指针, 可以使用关键字__strong 进行修饰一个变量。
B:弱指针:__weak 关键字修饰的变量。
在ARC中,弱指针指向的对象如果被销毁,此时弱指针值自动修改为nil。
注意:
所有关于内存的代码release,autorelease,retain,retainClount等不能调用。
允许重写dealloc方法,但是不能调用[super dealloc];
在ARC中,我们需要将@property的参数retain改为strong,意味着成员变量为强指针;
此时,weak代替assign,相当于assign,意味着成员变量为弱指针。
循环引用 {.p1}
在内存管理中,我们要注意的就是循环引用,循环引用是两个对象循环强引用。此时,两个对象都不能release,就不会被释放。
我们俩可以将其中一个对象的@property参数设置为weak。
如何检测循环引用,其实也是一个开发中的技巧。已经有很多人介绍过循环引用这儿的技巧和解决方案。Apple也是将view中的控件都默认为weak弱引用。
总结 {.p2}
今天总结的内容相对较多,同时内存管理也是iOS开发中很重要的一节内容,在这篇文章中,我并没有展开写,只是总结归纳了一下。如果堆内存管理不是很清楚的同学,建议多在此话费一点时间。在面试中,绝对不会逃过内存管理不问。下一篇准备总结OC中的又一个特色——Block代码块。