内存管理详解
基本原理
什么是内存管理
移动设备的内存极其有限,每个app所能占用的内存是有限制的
当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间。比如回收一些不需要使用的对象、变量等
管理范围:任何继承了NSObject的对象,对其他基本数据类型(int、char、float、double、struct、enum等)无效
对象的基本结构
每个OC对象都有自己的引用计数器,是一个整数,表示“对象被引用的次数”,即有多少人正在使用这个OC对象
每个OC对象内部专门有4个字节的存储空间来存储引用计数器
引用计数器的作用
当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1
当一个对象的引用计数器值为0时,对象占用的内存就会被系统回收。换句话说,如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收,除非整个程序已经退出
引用计数器的操作
给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身)
给对象发送一条release消息,可以使引用计数器值-1
可以给对象发送retainCount消息获得当前的引用计数器值 新版可以通过CFGetRetainCount(obj)获取
对象的销毁
当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收
当一个对象被销毁时,系统会自动向对象发送一条dealloc消息
一般会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言
一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用
不要直接调用dealloc方法
一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)
内存管理原则
原则分析
QQ堂开房间原理:只要房间还有人在用,就不会解散
只要还有人在用某个对象,那么这个对象就不会被回收
只要你想用这个对象,就让对象的计数器+1
当你不再使用这个对象时,就让对象的计数器-1
谁创建,谁release
如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease
换句话说,不是你创建的,就不用你去[auto]release
谁retain,谁release
只要你调用了retain,无论这个对象是如何生成的,你都要调用release
总结
有始有终,有加就有减
曾经让对象的计数器+1,就必须在最后让对象计数器-1
set方法的内存管理
如果你有个OC对象类型的成员变量,就必须管理这个成员变量的内存。比如有个Book *_book
### set方法的实现
- (void)setBook:(Book *)book{
if (book != _book) {
[_book release];
_book = [book retain];
}
}dealloc方法的实现
- (void)dealloc {
[_book 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方法的名称
一般来说,除了alloc、new或copy之外的方法创建的对象都被声明了autorelease
比如下面的对象都已经是autorelease的,不需要再release
NSNumber *n = [NSNumber numberWithInt:100];
NSString *s = [NSString stringWithFormat:@"jack"];
NSString *s2 = @"rose";AutoRelease
给某个对象发送一条autorelease消息时,就会将这个对象加到一个自动释放池中当自动释放池销毁时,会给池子里面的所有对象发送一条release消息
调用autorelease方法时并不会改变对象的计数器,并且会返回对象本身
autorelease实际上只是把对release的调用延迟了,对于每一次autorelease,系统只是把该对象放入了当前的autorelease pool中,当该pool被释放时,该pool中的所有对象会被调用Release
自动释放池的创建
ios 5.0后
ARC环境不能使用NSAutoreleasePool类也不能调用autorelease方法,代替它们实现对象自动释放的是@autoreleasepool块和__autoreleasing修饰符
@autoreleasepool
{
// ....
}ios 5.0前
在MRC环境中使用自动释放池需要用到NSAutoreleasePool对象,其生命周期就相当于C语言变量的作用域。对于所有调用过autorelease方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法
//MRC环境下的测试:
//第一步:生成并持有释放池NSAutoreleasePool对象;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//第二步:调用对象的autorelease实例方法;
id obj = [[NSObject alloc] init];
[obj autorelease];
//第三步:废弃NSAutoreleasePool对象;
[pool drain]; //向pool管理的所有对象发送消息,相当于[obj release]
//obi已经释放,再次调用会崩溃(Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT))
NSLog(@"打印obj:%@", obj);在程序运行过程中,可以创建多个自动释放池,它们是以栈的形式存在内存中(以栈为结点通过双向链表的形式组合而成)
OC对象只需要发送一条autorelease消息,就会把这个对象添加到最近的自动释放池中(栈顶的释放池)
## AutoreleasePool与NSThread、NSRunLoop的关系
由于AppKit和UIKit框架的优化,我们很少需要显式的创建一个自动释放池块。这其中就涉及到AutoreleasePool与NSThread、NSRunLoop的关系。
RunLoop和NSThread的关系
RunLoop是用于控制线程生命周期并接收事件进行处理的机制,其实质是一个do-While循环。在苹果文档找到关于NSRunLoop的介绍如下:
官方原文
Your application neither creates or explicitly manages NSRunLoop objects. Each NSThread object—including the application’s main thread—has an NSRunLoop object automatically created for it as needed. If you need to access the current thread’s run loop, you do so with the class method currentRunLoop.大致翻译
您的应用程序既不创建也不显式管理NSRunLoop对象。每个NSThread对象(包括应用程序的主线程)都有一个根据需要自动为其创建的NSRunLoop对象。如果需要访问当前线程的运行循环,可以使用类方法currentRunLoop。总结RunLoop与NSThread(线程)之间的关系如下:
RunLoop与线程是一一对应关系,每个线程(包括主线程)都有一个对应的RunLoop对象;其对应关系保存在一个全局的Dictionary里;
主线程的RunLoop默认由系统自动创建并启动;而其他线程在创建时并没有RunLoop,若该线程一直不主动获取,就一直不会有RunLoop;
苹果不提供直接创建RunLoop的方法;所谓其他线程Runloop的创建其实是发生在第一次获取的时候,系统判断当前线程没有RunLoop就会自动创建;
当前线程结束时,其对应的Runloop也被销毁;
RunLoop和AutoreleasePool的关系
官方原文
The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.大致翻译
Application Kit在事件循环的每个循环开始时在主线程上创建一个自动释放池,并在结束时将其清空,从而释放在处理事件时生成的任何自动释放对象。如上所述,主线程的NSRunLoop在监测到事件响应开启每一次event loop之前,会自动创建一个autorelease pool,并且会在event loop结束的时候执行drain操作,释放其中的对象。
Thread和AutoreleasePool的关系
官方原文
Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects (see Threads). As new pools are created, they get added to the top of the stack. When pools are deallocated, they are removed from the stack. Autoreleased objects are placed into the top autorelease pool for the current thread. When a thread terminates, it automatically drains all of the autorelease pools associated with itself.大致翻译
每个线程(包括主线程)维护自己的NSAutoreleasePool对象堆栈(参见Threads)。随着新池的创建,它们会被添加到堆栈的顶部。当池被释放时,它们将从堆栈中移除。自动释放对象被放在当前线程的顶层自动释放池中。当一个线程终止时,它会自动耗尽所有与自己关联的自动释放池。如上所述, 包括主线程在内的所有线程都维护有它自己的自动释放池的堆栈结构。新的自动释放池被创建的时候,它们会被添加到栈的顶部,而当池子销毁的时候,会从栈移除。对于当前线程来说,Autoreleased对象会被放到栈顶的自动释放池中。当一个线程线程停止,它会自动释放掉与其关联的所有自动释放池。