ObjC: 内存管理
对于使用比如Java这种自动内存垃圾回收语言的读者,第一次接触ObjC会感觉很不爽。开发的时候,每alloc一个对象,就要走脑子考虑一下需要在什么时候释放它。如果你忽略了这件事情,将会在监控窗口上看到内存在不断的增长,或者应用在运行一段时间后会自动退出。
精通ObjC内存管理技术,是编写合格iOS应用的重要条件,尤其是针对iPad或者长时间运行的应用(比如游戏)。
引用计数
ObjC管理内存的最基本概念是引用计数。比如,你创建了一个对象,那么该对象的引用技术就是1。如果这个对象被多个变量引用,则计数要累加(这需 要开发者编程),这些引用的变量,可能很快就不在使用了,那么计数要做减法(需要编程),当引用计数到0的时候,系统会销毁该对象,清空该对象使用的内 存。
有关引用计数方面的方法,在NSObject类中:
– retain required method,引用技术加1
– release required method,引用计数减1
– autorelease required method,?
– retainCount required method,返回当前对象引用计数
当创建对象,调用alloc类方法后,就会为对象引用计数加1,这时如果retainCount方法调用,将返回1。比如:
Book *book=[Book alloc];
NSLog(@"对象引用计数:%i",[book retainCount]);
book=[book init];
NSLog(@"对象引用计数:%i",[book retainCount]);
打印的内容:
2011-05-20 12:09:46.756 iOSSimpleDemo[9857:207] 对象引用计数:1
2011-05-20 12:09:46.758 iOSSimpleDemo[9857:207] 对象引用计数:1
那么如果:
Book *book=[Book alloc];
NSLog(@"对象引用计数:%i",[book retainCount]);
book=[book init];
NSLog(@"对象引用计数:%i",[book retainCount]);
[book retain];
NSLog(@"对象引用计数:%i",[book retainCount]);
[book release];
NSLog(@"对象引用计数:%i",[book retainCount]);
[book release];
NSLog(@"对象引用计数:%i",[book retainCount]);
日志:
可以看到,通过retain让引用计数加1,通过release让引用计数减1,如果减到0了,则该对象将被释放,再访问,就会报错。
另外,如果过度释放,比如引用计数为0了,再释放,也会报错:
iOSSimpleDemo(9999,0xa07be540) malloc: *** error for object 0x4b4a8e0: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
这里还有个方法:autorelease。说一下用它的基本原理。
我有一个类Book,有方法返回一个Author,Book头文件:
#import <Foundation/Foundation.h>
#import "Author.h"@interface Book : NSObject {
}
-(Author *)createAuthor;
@end
实现代码中使用了autorelease:
#import "Book.h"
@implementation Book
-(Author *)createAuthor{
Author *author=[[Author alloc] init];
return [author autorelease];
}@end
Author类的头文件没什么特别的:
#import <Foundation/Foundation.h>
@interface Author : NSObject {
}
@end
在Author实现文件中,覆盖了NSObject的dealloc方法:
#import "Author.h"
@implementation Author
- (void) dealloc{
NSLog(@"dealloc author.");
[super dealloc];
}@end
dealloc方法,是在release到内存计数为0的时候调用的。也就是清除内存中的该对象。如果调用的不是release,而是 autorelease,则会将该对象加到上下文的NSAutoreleasePool中,当NSAutoreleasePool调用drain方法的时 候释放该对象。这里需要创建Mac OS X命令行应用,因为这样可以很直白的调用NSAutoreleasePool。如果是iOS应用,后面会讲到,会有很多隐式调用 NSAutoreleasePool的地方。
main方法:
#import <Foundation/Foundation.h>
#import "Book.h"int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];Book *book=[[Book alloc] init];
Author *author=[book createAuthor];
NSLog(@"author: %@",author);
NSLog(@"start pool drain…");
[pool drain];
NSLog(@"pool drained.");
return 0;
}
可见日志:
2011-05-20 15:00:47.986 MemoryReleaseDemo[10895:a0f] author: <Author: 0x10010c880>
2011-05-20 15:00:47.989 MemoryReleaseDemo[10895:a0f] start pool drain…
2011-05-20 15:00:47.990 MemoryReleaseDemo[10895:a0f] dealloc author.
2011-05-20 15:00:47.990 MemoryReleaseDemo[10895:a0f] pool drained.
说明是调用:
[pool drain];
时做的dealloc。如果是release到内存技术为0的情况,应该是立即调用dealloc,读者可以自己测试验证一下。
引用计数的复杂性
引用计数看来很简单,但是当多个地方引用相同的对象时。对象要怎么释放,就会造成复杂的情况。
比如Book代码是这样的,它复合(包括)了一个成员,Author,Book头文件:
#import <Foundation/Foundation.h>
#import "Author.h"@interface Book : NSObject {
Author *author;
}@property(nonatomic,retain) Author *author;
@end
在这里retain表示,设置author给Book,Book实例会保持该对象,也就是内存计数加1。
Book实现文件:
#import "Book.h"
@implementation Book
@synthesize author;
- (void) dealloc{
[author release];
[super dealloc];
}@end
这里别忘记覆盖NSObject的dealloc方法,在里面释放author成员。如果author成员未赋值,也没关系,相当于[nil release],不会发生任何事情,包括异常。
再看看Author类,头文件没啥好说的:
#import <Foundation/Foundation.h>
@interface Author : NSObject {
}
@end
实现文件中还是覆盖了dealloc方法:
#import "Author.h"
@implementation Author
- (void) dealloc{
NSLog(@"dealloc author.");
[super dealloc];
}@end
在main函数的调用:
#import <Foundation/Foundation.h>
#import "Book.h"int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];Book *book=[[Book alloc] init];
Author *author=[[Author alloc] init];
book.author=author;
NSLog(@"author retain count: %i",[author retainCount]);
[author release];
author=[[Author alloc] init];
book.author=author;
NSLog(@"set new author");
[author release];
[book release];
[pool drain];
return 0;
}
日志:
2011-05-23 14:40:34.802 MemoryReleaseDemo[11994:a0f] author retain count: 2
2011-05-23 14:40:34.805 MemoryReleaseDemo[11994:a0f] dealloc author.
2011-05-23 14:40:34.805 MemoryReleaseDemo[11994:a0f] set new author
2011-05-23 14:40:34.806 MemoryReleaseDemo[11994:a0f] dealloc author.
可以看出,在第一次设置book.author属性的时候,引用计数增加到2。说明ObjC生成的setter方法内部肯定是做了retain操 作。在第二次book.author属性设置的时候,发现调用了一次Author的dealloc方法,即,设置第二个author实例给book对象, 在setter方法中释放了第一个author实例。
这里用@property举例子说明内存释放,是要说明如何使用@property属性。另外,@property属性是ObjC2.0新增的特性。它的作用是在编译代码时将@property相关语句生成为set方法:
-(void) setAuthor:(Author *)newAuthor{
[newAuthor retain];
[author release];
author=newAuthor;
}
这里代码的次序很重要,先retain了newAuthor,再释放author。原因是,如果颠倒次序,而且传入的是相同的对象,则会造成对象引用计数为0了。后面的操作都会出错。
Cocoa内存管理规则
Cocoa内存管理原则很简单:
- 当使用new,alloc或者copy方法创建对象的时候,该对象计数器为1。当不在使用的时候,要release释放该对象或者autorelease提交到对象自动释放池。
- 当通过其他方法获得对象,要假设该对象的计数器已经是1,而且被设置autorelease了。在获得该对象后不需要进行清理操作。如果打算使用一段时间,需要retain操作,并且在使用完毕做release释放它。
- 如果retain了某个对象。需要release或者autorelease该对象。并且保持retain和release方法调用的次数一样多。
举一个常见的例子。比如使用NSArray:
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];Author *author=[[Author alloc]init];
NSArray *array=[NSArray arrayWithObjects:author,nil];
NSLog(@"author retain count: %i",[author retainCount]);
[author release];
NSLog(@"author retain count: %i",[author retainCount]);
[pool drain];
return 0;
}
这里创建了一个author对象,并且把它放到NSArray中去了。当放入NSArray后,会发现author的引用计数为2了,也就是说在放入集合中后,引用计数会自动加1。
另外,array变量不是通过new、alloc或者copy创建的,因此我们假设(实际上就是)它会retain使引用计数为1了。而且还是autorelease的。因此上面的例子并未release array。
用户界面开发的特殊性
在用户界面开发的项目中,比如iOS项目(其实Mac OS X情况是类似的)。如果类似上面的代码中main函数的NSAutoreleasePool,那么在应用在运行时会产生大量的内存垃圾的。ObjC已经考 虑到这样的情况。并做了相应的处理机制,即上面提到的隐式NSAutoreleasePool机制。
ObjC在foundation kit中提供了一个类NSRunLoop,用来提供对输入资源管理编程接口。该对象表示一次界面交互过程。在这个过程中,iOS会创建一个隐式的 NSAutoreleasePool,供autorelease的对象保存之用。当这个runloop结束时清除这个NSAutoreleasePool 的全部对象。
ObjC: 内存管理
对于使用比如Java这种自动内存垃圾回收语言的读者,第一次接触ObjC会感觉很不爽。开发的时候,每alloc一个对象,就要走脑子考虑一下需要在什么时候释放它。如果你忽略了这件事情,将会在监控窗口上看到内存在不断的增长,或者应用在运行一段时间后会自动退出。
精通ObjC内存管理技术,是编写合格iOS应用的重要条件,尤其是针对iPad或者长时间运行的应用(比如游戏)。
引用计数
ObjC管理内存的最基本概念是引用计数。比如,你创建了一个对象,那么该对象的引用技术就是1。如果这个对象被多个变量引用,则计数要累加(这需 要开发者编程),这些引用的变量,可能很快就不在使用了,那么计数要做减法(需要编程),当引用计数到0的时候,系统会销毁该对象,清空该对象使用的内 存。
有关引用计数方面的方法,在NSObject类中:
– retain required method,引用技术加1
– release required method,引用计数减1
– autorelease required method,?
– retainCount required method,返回当前对象引用计数
当创建对象,调用alloc类方法后,就会为对象引用计数加1,这时如果retainCount方法调用,将返回1。比如:
Book *book=[Book alloc];
NSLog(@"对象引用计数:%i",[book retainCount]);
book=[book init];
NSLog(@"对象引用计数:%i",[book retainCount]);
打印的内容:
2011-05-20 12:09:46.756 iOSSimpleDemo[9857:207] 对象引用计数:1
2011-05-20 12:09:46.758 iOSSimpleDemo[9857:207] 对象引用计数:1
那么如果:
Book *book=[Book alloc];
NSLog(@"对象引用计数:%i",[book retainCount]);
book=[book init];
NSLog(@"对象引用计数:%i",[book retainCount]);
[book retain];
NSLog(@"对象引用计数:%i",[book retainCount]);
[book release];
NSLog(@"对象引用计数:%i",[book retainCount]);
[book release];
NSLog(@"对象引用计数:%i",[book retainCount]);
日志:
可以看到,通过retain让引用计数加1,通过release让引用计数减1,如果减到0了,则该对象将被释放,再访问,就会报错。
另外,如果过度释放,比如引用计数为0了,再释放,也会报错:
iOSSimpleDemo(9999,0xa07be540) malloc: *** error for object 0x4b4a8e0: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
这里还有个方法:autorelease。说一下用它的基本原理。
我有一个类Book,有方法返回一个Author,Book头文件:
#import <Foundation/Foundation.h>
#import "Author.h"@interface Book : NSObject {
}
-(Author *)createAuthor;
@end
实现代码中使用了autorelease:
#import "Book.h"
@implementation Book
-(Author *)createAuthor{
Author *author=[[Author alloc] init];
return [author autorelease];
}@end
Author类的头文件没什么特别的:
#import <Foundation/Foundation.h>
@interface Author : NSObject {
}
@end
在Author实现文件中,覆盖了NSObject的dealloc方法:
#import "Author.h"
@implementation Author
- (void) dealloc{
NSLog(@"dealloc author.");
[super dealloc];
}@end
dealloc方法,是在release到内存计数为0的时候调用的。也就是清除内存中的该对象。如果调用的不是release,而是 autorelease,则会将该对象加到上下文的NSAutoreleasePool中,当NSAutoreleasePool调用drain方法的时 候释放该对象。这里需要创建Mac OS X命令行应用,因为这样可以很直白的调用NSAutoreleasePool。如果是iOS应用,后面会讲到,会有很多隐式调用 NSAutoreleasePool的地方。
main方法:
#import <Foundation/Foundation.h>
#import "Book.h"int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];Book *book=[[Book alloc] init];
Author *author=[book createAuthor];
NSLog(@"author: %@",author);
NSLog(@"start pool drain…");
[pool drain];
NSLog(@"pool drained.");
return 0;
}
可见日志:
2011-05-20 15:00:47.986 MemoryReleaseDemo[10895:a0f] author: <Author: 0x10010c880>
2011-05-20 15:00:47.989 MemoryReleaseDemo[10895:a0f] start pool drain…
2011-05-20 15:00:47.990 MemoryReleaseDemo[10895:a0f] dealloc author.
2011-05-20 15:00:47.990 MemoryReleaseDemo[10895:a0f] pool drained.
说明是调用:
[pool drain];
时做的dealloc。如果是release到内存技术为0的情况,应该是立即调用dealloc,读者可以自己测试验证一下。
引用计数的复杂性
引用计数看来很简单,但是当多个地方引用相同的对象时。对象要怎么释放,就会造成复杂的情况。
比如Book代码是这样的,它复合(包括)了一个成员,Author,Book头文件:
#import <Foundation/Foundation.h>
#import "Author.h"@interface Book : NSObject {
Author *author;
}@property(nonatomic,retain) Author *author;
@end
在这里retain表示,设置author给Book,Book实例会保持该对象,也就是内存计数加1。
Book实现文件:
#import "Book.h"
@implementation Book
@synthesize author;
- (void) dealloc{
[author release];
[super dealloc];
}@end
这里别忘记覆盖NSObject的dealloc方法,在里面释放author成员。如果author成员未赋值,也没关系,相当于[nil release],不会发生任何事情,包括异常。
再看看Author类,头文件没啥好说的:
#import <Foundation/Foundation.h>
@interface Author : NSObject {
}
@end
实现文件中还是覆盖了dealloc方法:
#import "Author.h"
@implementation Author
- (void) dealloc{
NSLog(@"dealloc author.");
[super dealloc];
}@end
在main函数的调用:
#import <Foundation/Foundation.h>
#import "Book.h"int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];Book *book=[[Book alloc] init];
Author *author=[[Author alloc] init];
book.author=author;
NSLog(@"author retain count: %i",[author retainCount]);
[author release];
author=[[Author alloc] init];
book.author=author;
NSLog(@"set new author");
[author release];
[book release];
[pool drain];
return 0;
}
日志:
2011-05-23 14:40:34.802 MemoryReleaseDemo[11994:a0f] author retain count: 2
2011-05-23 14:40:34.805 MemoryReleaseDemo[11994:a0f] dealloc author.
2011-05-23 14:40:34.805 MemoryReleaseDemo[11994:a0f] set new author
2011-05-23 14:40:34.806 MemoryReleaseDemo[11994:a0f] dealloc author.
可以看出,在第一次设置book.author属性的时候,引用计数增加到2。说明ObjC生成的setter方法内部肯定是做了retain操 作。在第二次book.author属性设置的时候,发现调用了一次Author的dealloc方法,即,设置第二个author实例给book对象, 在setter方法中释放了第一个author实例。
这里用@property举例子说明内存释放,是要说明如何使用@property属性。另外,@property属性是ObjC2.0新增的特性。它的作用是在编译代码时将@property相关语句生成为set方法:
-(void) setAuthor:(Author *)newAuthor{
[newAuthor retain];
[author release];
author=newAuthor;
}
这里代码的次序很重要,先retain了newAuthor,再释放author。原因是,如果颠倒次序,而且传入的是相同的对象,则会造成对象引用计数为0了。后面的操作都会出错。
Cocoa内存管理规则
Cocoa内存管理原则很简单:
- 当使用new,alloc或者copy方法创建对象的时候,该对象计数器为1。当不在使用的时候,要release释放该对象或者autorelease提交到对象自动释放池。
- 当通过其他方法获得对象,要假设该对象的计数器已经是1,而且被设置autorelease了。在获得该对象后不需要进行清理操作。如果打算使用一段时间,需要retain操作,并且在使用完毕做release释放它。
- 如果retain了某个对象。需要release或者autorelease该对象。并且保持retain和release方法调用的次数一样多。
举一个常见的例子。比如使用NSArray:
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];Author *author=[[Author alloc]init];
NSArray *array=[NSArray arrayWithObjects:author,nil];
NSLog(@"author retain count: %i",[author retainCount]);
[author release];
NSLog(@"author retain count: %i",[author retainCount]);
[pool drain];
return 0;
}
这里创建了一个author对象,并且把它放到NSArray中去了。当放入NSArray后,会发现author的引用计数为2了,也就是说在放入集合中后,引用计数会自动加1。
另外,array变量不是通过new、alloc或者copy创建的,因此我们假设(实际上就是)它会retain使引用计数为1了。而且还是autorelease的。因此上面的例子并未release array。
用户界面开发的特殊性
在用户界面开发的项目中,比如iOS项目(其实Mac OS X情况是类似的)。如果类似上面的代码中main函数的NSAutoreleasePool,那么在应用在运行时会产生大量的内存垃圾的。ObjC已经考 虑到这样的情况。并做了相应的处理机制,即上面提到的隐式NSAutoreleasePool机制。
ObjC在foundation kit中提供了一个类NSRunLoop,用来提供对输入资源管理编程接口。该对象表示一次界面交互过程。在这个过程中,iOS会创建一个隐式的 NSAutoreleasePool,供autorelease的对象保存之用。当这个runloop结束时清除这个NSAutoreleasePool 的全部对象。