ios内存管理
一.前言
在ios中,系统对每个程序运行时内存的占有都有一个限制,默认都是几十兆左右,当程序占用内存的大小超过限制时,程序可能就会被强制退出.
在内存中,分为堆和栈,栈中主要存放变量,堆中主要存放对象。栈中的东西是系统自动收回的,当一个变量使用完成后,存放在栈中的东西会立即被收回。但堆中存储的东西是不会被随便回收的。
由于移动设备内存有限,所以我们会对内存进行严格的管理,以避免内存泄露造成资源浪费。
在OC中,只有对象才属于内存管理范围,例如int ,float,struct等基本数据类型不存在内存管理的概念,在ios开发中,对内存的管理实际上是对引用计数器的管理。
相关概念:
僵尸对象:所占用的内存被回收的对象,僵尸对象不能再使用。
野指针:指向僵尸对象的指针,给野指针发消息会报错。
空指针:没有指向任何东西的指针,给空指针发送消息不会报错。
目前OC内存管理方式有三种:
(1)自动垃圾收集(Automatic Garbage Collection)
(2)MRC(Manual Reference Counting,手动引用计数器)和自动释放池(Autorelease)
(3)ARC(Automatic Reference Counting,自动引用计数器)
自动垃圾收集:
在OC2.0中,有一种自动垃圾收集的内存管理形式,通过垃圾自动收集,系统能检测出对象是否拥有其他对象,当程序运行期间,不被引用的对象就会被释放。
但是在ios运行环境中不支持自动垃圾收集,在OS X环境才支持,不过苹果推荐使用ARC进行替代。
MRC和自动释放池子:
1. 引用计数器的概念:
即一个对象被引用的次数,每个对象的引用计数器占4个字节。
说明当一个对象被创建的时候,该对象的默认RC为1,当该对象被引用一次,需要调用retain方法,使RC的值加1,当指针失去对该对象的引用,需要调用release方法,使RC的值减1,当RC的值等于0时,该对象被系统自动销毁回收。
2.对象的销毁
当一个对象的RC为0时,那么它将被销毁,其占用的内存会被系统回收.
当一个对被销毁时,系统会自动向对象发送一条dealloc消息
一般会重写dealloc方法,在这里释放相关资源,dealloc就是对象的遗言
一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用
对象一旦被回收了,它占用的内存不再可用,坚持使用就会导致指针错误
3.MRC
通过人为的方式来控制引用计数器的增减,影响对象RC值的方式有以下几种:
(1)new, alloc,copy,mutableCopy这几种方式来创建一个新的对象,此时RC的默认值为1.
(2)retain,对象调用retain方法,RC的值加1.
(3)release,对象调用release方法,RC的值减1.
(4)dealloc,dealloc方法并不会影响RC值,但是当RC值为0时,系统会调用dealloc方法来销毁对象.
例如:
通过上面代码我们知道,成员变量的设值和取值方法是手动生成的,而且setter方法中成员变量的引用计数器也是手动设置,我们可以通过@property和相应关键字来由编译器生成.
关于在MRC中@property关键字如下:
(1)assign和retain和copy
这几个关键字用于setter方法的内存管理,如果使用assign(一般用于非OC对象),那么将直接进行赋值操作;如果使用retain(一般用于OC对象),那么将retain新值,release旧值;如果使用copy,那么将release旧值,copy新值。
(2)nonatomic和atomic
这两个关键字用于多线程管理,nonatomic的性能高,atomic的性能低.不显示则atomic为默认值
(3)readwrite和readonly
这两个关键字说明是否生成setter方法,readwrite将自动生成setter方法和getter方法。readonly则只生成getter方法
如果使用@property属性,上述代码可以改为:
4.循环引用内存管理原则
对于两个类B包含A,A包含B的循环引用情况下,看如下代码:
当执行Book*b1=[[Book1 alloc]init]后;b1指向Book1。
当执行Book2*b2=[[Book2 alloc]init]后,b2指向Book2。
当执行b1.book2=b2后;Book1中的成员变量_book2指向Book2。
当执行b2.book1=b1后;Book2中的成员变量_book1指向Book1。
此时Book1的引用计数器RC=2;Book2的引用计数器RC=2。
当执行[b1 release]后,b1释放对Book1的控制权,此时Book1的引用计数器RC=2-1=1。
当执行[b2 release]后,b2释放对Book2的控制权,此时Book2的引用计数器RC=2-1=1。
那么由于仍有指针指向Book1和Book2,所以Book1和Book2不会被销毁释放,这样就造成内存泄露.
对于上面的情况,只需要在Book1或者Book2的任意一端使用assign,如此就能避免互相引用。
对于下面这种循环引用情况,只能使用assign。
如果@property(nonatomic,assign)id instance中的assign换成retain,也会造成内存泄露,因为id为泛型。
5.Autorelease Pool的使用
顾名思义,Autorelease即自动释放对象,不需要我们手动释放。从上面的代码中可以知道,在main函数中,创建对象obj后,总需要调用[obj release]方法,这样无疑工作量变大,且对我们的工作无多大意义。为了减少这种无意义的工作,我们可以使用Autorelease Pool。
在ios程序运行的过程中,会创建无数个池子。这些池子都是以栈结构存在(先进后出),当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池。
Autorelease Pool即自动释放池,在Autorelease Pool内的对象,其release方法最终由编译器自动完成,而不需要手动执行。
autorelease的具体使用方法:
(1)生成并持有NSAutoreleasePool对象
(2)调用已分配对象的autorelease实例方法
(3)废弃NSAutoreleasePool对象
那到底什么时候废弃NSAutoreleasePool对象呢?
在Cocoa框架中,相当于程序住循环的NSRunLoop或者在其他程序可运行的地方,对NSAutoreleasePool对象进行生成、持有、和废弃处理。如下图:
NSAutoreleasePool对象的生命周期如下:
NSAutoreleasePool的创建有两种方式:
在返回对象的方法中最好使用自动释放池释放对象,由于在返回该对象之前不能释放该对象,所以可以通过自动释放池来延迟该对象的释放。如:
如果在一个循环中产生了大量的临时对象,可以在循环的内部提供一个自动释放池在下一次迭代前来清除这些对象,以减少最大内存占用。
6.ARC
ARC是编译器特性,可以理解为xcode的功能。它主要用来帮用户做内存管理工作,优化开发流程。
特别注意的是它与java的垃圾回收机制不是同一个东西,它仅是xcode的一个功能而已。在ARC下,RC由编译器自动管理,不需要手动管理。
ARC判断标准:
只要没有强指针指向该对象,该对象就会被释放。就算此时有弱指针指向该对象,或者该对象还指向其他对象,只要没有强指针指向其,其仍旧被释放。
ARC特点:
(1)不允许调用release、retain、retainCount
(2)@property的参数
*strong:成员变量是强指针(适用于OC对象)
*weak:成员变量是弱指针(适用于OC对象)
*assign:适用于非OC对象
(3)MRC下的retain改用strong
在实际项目中,某些文件需要用到release,retain方法,比如下载的第三方框架中如果有release方法,放在支持ARC的环境下会报错。这时我们可以设置这些文件不使用ARC,其他支持ARC环境的文件继续使用ARC,相应的方法是选择项目的Build phases,在Compile Sources中选择不需要ARC的文件,双击或回车,在弹出的窗口中输入-fno-objc-arc即可。同理可在MRC环境中使用-f-objc-arc使相关文件支持ARC.
ARC模式下,创建新对象通常由以下几种关键字来限定。
(1)_strong(默认值),由_strong修饰的为强指针,对象只要有强指针指向它就不会被销毁,每当一个强指针指向它,该对象的RC+1。
(2)_weak,由_weak修饰的为弱指针,弱指针指向的对象不会影响该对象的RC值,当弱指针指向的对象被销毁时,该指针的值为nil(即该指针指向一个nil对象)。
(3)_unsafe_unretained,该类型指针不会影响所指对象的RC值,当其所指向的对象被销毁时,该指针的值不会变为nil,仍保留原有的地址(即仍然指向已被释放对象的地址,因此才会_unsafe)。
注意:ARC模式下仍能使用自动释放池。