1.为什么需要管理内存
移动设备的内存是有限的,每一个app所能占用的内存是有限的,如果不进行内存管理,那么app就会出现闪退,崩溃等情况。
2.什么是内存管理
内存管理是指软件运行时,对内存资源的分配和使用技术,其最主要的目的是如何高效,快速的分配内存,并且在适当的时候释放和回收内存资源。
3.如何进行内存管理
iOS开发中数据一般是存储在堆和栈中的,然后栈内存会自动回收,并不需要我们进行手动管理,因此,我们要管理的就是存放在堆内存中的数据。
那么,堆和栈都是用来存储什么数据的呢?
内存中有五大区域:栈,堆,BSS段,数据段,代码段,这五大区域的作用分别是:
栈:用来存储局部变量
堆:用来存储对象,允许程序员手动申请
BSS段:未初始化的全局变量,静态变量
数据段:初始化的全局变量,静态变量和常量
代码段:代码
因此,一般情况下,我们只需要关注对象什么时候创建,什么时候释放即可。
4.引用计数器
(1)什么是引用计数器?
每个OC对象都有自己的引用计数器,它是一个整数
可以理解为有多少个人正在使用或者被引用了多少次
(2)系统是如何判断什么时候需要回收一个对象的?
根据对象的引用计数器属。
当对象的引用计数器为0时,这个对象就会被释放,只要这个对象的引用计数器不为0,就不会被释放。
当创建(alloc,new,copy)一个对象的实例,并在堆内存上申请内存时,对象的引用计数器就为1。在其它对象中需要使用这个对象时,这个对象的引用计数器就会+1,需要释放一个对象时,就将这个对象的引用计数器-1,直到对象的引用计数为0,这个对象就会被自动释放
(3)如何操作一个对象的引用计数器?
给对象发送一个retain消息,可以使引用计数器值+1
给对象发送一个release消息,可以使引用计数器-1
给对象发送一个retainCount消息,可以方便程序员查看引用计数器的值,但是这个值并不都是正确的。
注意:给对象发送release消息,并不代表这个对象就一定会释放,只有当对象的引用计数器为0时,这个对象才会释放。
(4)dealloc方法
dealloc是一个对象方法,dealloc方法和load,initialization是一样的,都不能手动调用,都是系统自动调用的。
5.单个对象的内存管理
int main(int argc, const char * argv[]){
@autoreleasepool{
Person *p = [[Person alloc] init]; //1
//指针p是一个局部变量,所以p存储在栈中,Person对象存储在堆内存中,所以,当大括号结束的时候,p就会自动释放了,但是因为Person对象在堆内存中,并不会自动释放,所以,需要手动释放。
[p release]; //如果直接这样写,会报错,因为是在ARC模式下,需要把程序切换到MRC模式下才不会报错。
}
}
@implementation Person
{
-(void)dealloc{
NSLog(@"%s", __func__);
}
[super dealloc];
}
注意:
(1)ARC:Automatic Reference Counting(自动引用计数)
不需要程序员管理,编译器xcode会在适当的地方自动加上release/retain等代码
MRC:Matual Reference Counting(手动引用计数)
需要手动添加release/retain等代码
(2)将项目改成MRC模式的:项目->搜索Automatic Reference Counting改为NO
内存管理的原则:一次alloc对应一次release,一次retain对应一次release,一次copy对应一次release,一次new对应一次release
6.多个对象的内存管理
存在依赖关系的对象之间的内存管理
比如:
@interface Room
@property int no;
@end
@implementation Room
-(void)dealloc{
NSLog(@"%s",__func__);
}
@end
@class Room;
@interface Person : NSObject
{
Room *_room;
}
-(void)setRoom:(Room *)room;
-(Room *)room;
@end
@implementation Person
-(void)setRoom:(Room *)room{
_room = room;
}
-(Room *)room{
return _room;
}
-(void)dealloc{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char *argv[]){
Person *p = [[Person alloc] init];
Room *r = [[Room alloc] init];
r.no = 888;
p._room = r;
[r release];
[p release];
}
//如果只是这样写的话,
当程序执行到第3行的时候,数据在内存中的存储形式如图1所示。
当执行[r release]的时候,Room对象的retainCount减1,这时Room对象就会被释放。
但是,此时Person对象还没被释放,Person对象还没有被释放时,Room对象是不能被释放的。因此,这样写是不对的。
因此,对上面的代码进行修改
@interface Room
@property int no;
@end
@implementation Room
-(void)dealloc{
NSLog(@"%s",__func__);
}
@end
@class Room;
@interface Person : NSObject
{
Room *_room;
}
-(void)setRoom:(Room *)room;
-(Room *)room;
@end
@implementation Person
-(void)setRoom:(Room *)room{
[room retain]; //当person对象使用room对象时,要对room对象的引用计数器加1
_room = room;
}
-(Room *)room{
return _room;
}
-(void)dealloc{
NSLog(@"%s",__func__);
[_room release]; //同时Person对象释放的时候,room对象也要释放
}
@end
int main(int argc, const char *argv[]){
Person *p = [[Person alloc] init];
Room *r = [[Room alloc] init];
r.no = 888;
p._room = r;
[r release];
[p release];
}
这样修改之后,再执行上面的代码时,room对象的引用计数器会变成2。再执行[r release]和[p release]时,就不会存在person对象还存在的情况下,room对象已经销毁的情况了,同时,Person对象销毁的同时,room对象也要销毁,这样才不会造成内存泄露。
接下来我们再看:
@interface Room
@property int no;
@end
@implementation Room
-(void)dealloc{
NSLog(@"%s",__func__);
}
@end
@class Room;
@interface Person : NSObject
{
Room *_room;
}
-(void)setRoom:(Room *)room;
-(Room *)room;
@end
@implementation Person
-(void)setRoom:(Room *)room{
[room retain]; //当person对象使用room对象时,要对room对象的引用计数器加1
_room = room;
}
-(Room *)room{
return _room;
}
-(void)dealloc{
NSLog(@"%s",__func__);
[_room release]; //同时Person对象释放的时候,room对象也要释放
}
@end
int main(int argc, const char *argv[]){
Person *p = [[Person alloc] init];
Room *r = [[Room alloc] init];
Room *r1 = [[Room alloc] init];
r.no = 888;
p._room = r;
p._room = r1;
[r release];
[p release];
}
如图2所示,换房的过程会造成内存泄露
因此在更换取值之前,应该将之前的房间释放掉
@interface Room
@property int no;
@end
@implementation Room
-(void)dealloc{
NSLog(@"%s",__func__);
}
@end
@class Room;
@interface Person : NSObject
{
Room *_room;
}
-(void)setRoom:(Room *)room;
-(Room *)room;
@end
@implementation Person
-(void)setRoom:(Room *)room{
if(_room != room){
[_room release]; //需要将之前的取值释放掉
}
[room retain]; //当person对象使用room对象时,要对room对象的引用计数器加1
_room = room;
}
-(Room *)room{
return _room;
}
-(void)dealloc{
NSLog(@"%s",__func__);
[_room release]; //同时Person对象释放的时候,room对象也要释放
}
@end
int main(int argc, const char *argv[]){
Person *p = [[Person alloc] init];
Room *r = [[Room alloc] init];
Room *r1 = [[Room alloc] init];
r.no = 888;
p._room = r;
p._room = r1;
[r release];
[p release];
}
因此,setter方法最终的结果就是这样的了。如果对象的属性是一个对象,那就需要这样写setter和dealloc方法。
7.property的修饰符
retain:会自动帮我们生成setter/getter方法内存管理的代码
assign:不会自动帮我们生成setter/getter方法内存管理的代码
8.autorelease
autorelease是一种支持引用计数的内存管理方式,只要给对象发送一条autorelease消息,会将对象放到一个自动释放池中,当自动释放池被销毁时,会对池子里的所有对象发送一条release消息
@autoreleasepool {
Person *p = [[Person alloc] init];
[p autorelease];
//model can dongSomething you want
NSLog(@"自动释放:end");
} //自动释放池销毁
//当执行到[p autorelease]时,对象会被加入到自动释放池中,当程序结束时,自动释放池会销毁,同时给池中的所有对象发送一个release消息
其它写法
//创建一个自动释放池
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Person *p = [[[Person alloc] init] autorelease];
[pool release];
使用优点:
再也不用考虑什么时候写release
其实是延迟了对象的release
在一个程序中可以使用多个自动释放池,多个自动释放池也可以嵌套使用
@autoreleasepool{
@autoreleasepool{
@autoreleasepool{
Person *p = [[Person alloc] init];
[p run];
}
}
}
多个自动释放池是以栈的形式存储的。遵循先进后出的原则。
给一个对象发送一条autorelease消息,那么会直接把对象放到栈顶的自动释放池。
注意:
(1) [p autorelease]会返回对象本身。
(2) 自动释放池被销毁时,只是会给池子中的所有对象发送一个release消息,并不是销毁对象
(3) autorelease并不会修改对象的引用计数器
(4) autoreleasepool自动释放池可以创建多个
(5) 一定要在自动释放池中调用autorelease,才会将对象放入自动释放池中
(6)在自动释放池中创建了对象一定要调用autorelease,才会将对象放入自动释放池中
(7)如果创建的对象只使用1次,以后就不用了,则不建议在autoreleasepool中使用
(8)尽量不要在自动释放池中使用循环次数较多的对象
(9)千万不要过度释放,一个alloc对应一个autorelease或者一个release。
(10)Person *p = [[[Person alloc] init] autorelease]
可以一创建就使用autorelease
(11)也可以使用类工厂方法,将autorelease封装到类工厂方法中,MRC模式下Foundation库中的类工厂方法都封装了autorelease