OC中内存管理的总结
想必用过iPhone,Android的很多用户都会感觉iPhone在使用过程中相对比较流畅,想必一般的Android手机基本不会出现卡顿的现象,让用户有很好的用户体验.究其原因当然有很多,在此我简单总结下iOS开发中的关于内存管理的一些知识,就当开发过程中的学习笔记咯.
OC中内存管理的本质
iOS开发中,OC中的内存管理实际上主要指的是对OC对象的占用内存的管理.
那么问题来了,为什么要管理OC对象的内存呢?这就要涉及OC程序中的内存划分,这里要考虑内存的堆内存部分与栈内存部分.
栈内存部分中的内存是用系统管理的的,而堆内存部分的内存是要自己管理(干预)的,OC的对象都是存放在堆内存中.所以需要我们自己管理.
OC对象是占用堆内存的.什么时候分配内存,什么时候回收内存是我们要考虑的问题.
而OC对象内存的分配与回收的实际上可以等价于OC对象自动引用计数的管理.通过对OC对象的自动引用计数的加减实现对象内存的分配与回收.当一个OC对象的自动引用计数变为0,此时这个对象就会被系统回收.所以,我们要实现对OC对象的内存管理就是要实现对OC对象的自动引用计数的控制.
在MRC时代
内存管理的管理范围
- 任何继承了NSObject的对象(OC对象存放于堆里面)
- 对其他非对象类型无效(int,char,float,double,struct,enum等),因为非OC对象一般放在栈里面(栈内存会被系统自动回收)
系统是如何判断什么时候需要回收一个对象所占用的内存?
根据对象的引用计数器.那么什么是引用计数呢?每个OC对象都有自己的引用计数器,它是一个整数,从字面上, 可以理解为”对象被引用的次数”,也可以理解为: 它表示有多少人正在用这个对象.每个OC对象内部都有4个字节的存储空间来存放引用计数器.
简单来说, 可以理解为: 引用计数器表示有多少人正在使用这个对象.当没有任何人使用这个对象时, 系统才会回收这个对象, 也就是说当对象的引用计数器为0时, 对象占用的内存就会被系统回收;如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收(除非整个程序已经退出).
任何一个对象, 刚生下来的时候, 引用计数器都为1,当使用alloc、new或者copy创建一个对象时,对象的引用计数器默认就是1.
引用计数器的操作
要想管理对象占用的内存, 就得学会操作对象的引用计数器
引用计数器的常见操作:
- 给对象发送一条retain消息, 可以使引用计数器值+1(retain方法返回对象本身)
- 给对象发送一条release消息, 可以使引用计数器值-1
- 给对象发送retainCount消息, 可以获得当前的引用计数器值
需要注意的是: release并不代表销毁\回收对象, 仅仅是计数器-1
dealloc方法
那么怎样验证一个OC对象是否被销毁了呢?
当一个对象的引用计数器值为0时,这个对象即将被销毁,其占用的内存被系统回收,系统会自动给对象发送一条dealloc消息.(因此, 从dealloc方法有没有被调用, 就可以判断出对象是否被销毁)
dealloc方法的重写
- 一般会重写dealloc方法, 在这里释放相关资源, dealloc就是对象的遗言
- 一旦重写了dealloc方法, 就必须调用[super dealloc],并且放在最后面调用
- 使用注意:不能直接调用dealloc方法,一旦对象被回收了, 它占用的内存就不再可用, 坚持使用会导致程序崩溃(野指针错误)
野指针/空指针
- 僵尸对象:已经被销毁的对象(不能再使用的对象)
- 野指针:指向僵尸对象(不可用内存)的指针,给野指针发消息会报EXC_BAD_ACCESS错误.
- 空指针:没有指向存储空间的指针(里面存的是nil, 也就是0),给空指针发消息是没有任何反应的
- 为了避免野指针错误的常见办法,在对象被销毁之后, 将指向对象的指针变为空指针
关闭ARC功能
Xcode4.2开始出现ARC,要回到MRC来自己管理对象的内存问题时要关闭项目ARC功能.
关闭ARC步骤如下: Build Settings ->Automatic Reference Counting(search)-> No
开启僵尸对象监控
监听僵尸对象->默认不监听-> Edit schemes->diagnostics->enable zombie objects
内存管理原则
苹果官方规定的内存管理原则:
- 谁创建谁release : 如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease
- 谁retain谁release :只要你调用了retain,就必须调用一次release
- 总结一下就是:有加就有减,曾经让对象的计数器+1,就必须在最后让对象计数器-1.
set方法的内存管理
- (void)setHouse:(House *)house{
if (house != _house){
// 对当前正在使用的房子(旧房子)做一次release
[_house release];
// 对新房子做一次retain操作
_house = [house retain];
}
}
dealloc方法的内存管理
- (void)dealloc{
// 当人不在了,代表不用房子了
// 对房子做一次release操作
[_house release];
[super dealloc];
}
@property参数
控制set方法的内存管理:
- retain : release旧值,retain新值(用于OC对象)
- assign : 直接赋值,不做任何内存管理(默认,用于非OC对象类型)
- copy : release旧值,copy新值(一般用于NSString *)
控制需不需生成set方法:
- readwrite:同时生成set方法和get方法(默认)
- readonly:只会生成get方法
多线程管理:
- atomic:性能低(默认)
- nonatomic:性能高
控制set方法和get方法的名称:
- setter : 设置set方法的名称,一定有个冒号:
- getter : 设置get方法的名称
@class
作用:可以简单地引用一个类.仅仅是告诉编译器有这样一个类,而不真正包含这个类中的所有的内容
具体使用:在.h文件中使用@class引用一个类,在.m文件中使用#import包含这个类的.h文件
@class和#import
作用上的区别:import会包含引用类的所有信息(内容),包括引用类的变量和方法,@class仅仅是告诉编译器有这么一个类,具体这个类里有什么信息,完全不知
效率上的区别:如果有上百个头文件都#import了同一个文件,或者这些文件依次被#import,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,编译效率非常低.相对来讲,使用@class方式就不会出现这种问题了
@class可以解决循环依赖的问题
循环retian
循环retain的场景:比如A对象retain了B对象,B对象retain了A对象
循环retain的弊端:这样会导致A对象和B对象永远无法释放
循环retain的解决方案:当两端互相引用时,应该一端用retain、一端用assign
autorelease
autorelease方法的基本作用:
- 给对象发送一条autorelease消息, 会将对象放到一个自动释放池中
- 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
- 会返回对象本身
- 调用完autorelease方法后,对象的计数器不变
autorelease的好处:
- 不用再关心对象释放的时间
- 不用再关心什么时候调用release
autorelease的使用注意:
- 占用内存较大的对象不要随便使用autorelease
- 占用内存较小的对象使用autorelease,没有太大影响
自动释放池
在iOS程序运行过程中,会创建无数个池子.这些池子都是以栈结构存在(先进后出).当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池
autorelease的应用场合
一般可以为类添加一个快速创建对象的类方法
一般来说,除了alloc、new或copy之外的方法创建的对象都被声明了autorelease,不需要再release
在ARC时代
ARC: Automatic Reference Counting(自动引用计数),Xcode4.2开始出现
从iOS 5.0开始ARC
ARC是编译器特性,而不是运行时特性
ARC不是像Java中的垃圾回收,有着本质的区别
ARC的优点
基本上可以避免内存泄漏,有时更快
默认情况下都是强指针,ARC中没有任何强指针指向时就会被销毁
ARC跟计数器关系不大,
__weak,弱指针指向对象被释放,弱指针会自动变成空指针而不是野指针
__strong,默认情况下所有指针都是强指针,要注意循环引用的问题
@property中多strong,weak两个参数
MRC项目转ARC项目
Xcode –> Edit –> Refactor –>Convert to Objective-C ARC… –>Convert to Automatic Reference Counting –>next
以前MRC中的retain全部变为strong,release在ARC中全部消失
关闭ARC Build Settings ->search(Automatic Reference Counting)->No