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]);

日志:

image

可以看到,通过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的全部对象。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值