本文记录Objective-C在内存管理方面的一些注意点。另有一篇转载的未公开笔记——Objective-C内存管理机制学习笔记【转】。html
在引用计数中,每个对象负责维护对象全部引用的计数值。编程
对象的初始引用计数值为1。若是引用计数的值为0,则对象就会自动dealloc。ide
包含alloc/new/copy/mutableCopy的方法 引用计数+1函数
retain 引用计数+1学习
release 引用计数-1ui
在 Objective-C 有2种管理内存的方法, 1) retain and release or 2) retain and release/autorelease。url
对于每一个 retain,必定要对应一个 release 或一个 autorelease。spa
查看方法:[object retainCount];指针
生成并持有对象 | alloc/new/copy/mutableCopy等方法 |
持有对象 | retain方法 |
释放对象 | release方法 |
废弃对象 | deallco方法 |
我的理解,所谓持有对象,能够简单理解为负责该对象的释放。code
1. obj对象建立后, 计数为1
2. obj对象加入到array中后,计数+1,为2
3. obj对象从array中移除后,计数-1,为1
Object *obj = [[Object alloc] init]; //1 [array addObject:obj]; //2 [array removeObjectAtIndex:0]; //1 /*此时obj的引用计数为1,内存泄漏*/
addObject和removeObjectAtIndex是一对,由系统管理引用计数。而咱们输入的Object *obj = [[Object alloc] init];并无一个release与之对应,因此形成obj没有被正确释放。
解决方法是在obj对象添加到array后,release它。
Object *obj = [[Object alloc] init]; //1 [array addObject:obj]; //2 [obj release]; //1 [array removeObjectAtIndex:0]; //0 /*此时obj的引用计数为0,内存不泄漏*/
NSArray* immutableArray = [[NSArray alloc] initWithArray:mutableArray] NSArray* immutableArray = [NSArray arrayWithArray:mutableArray]; NSArray* immutableArray = [mutableArray copy];
1. alloc和copy都会分配内存,须要手动release。因此调用第一个和第三个都须要 [immutableArray release].
2. arrayWithArray也会分配内存,不过系统会来管理这块内存,不须要手动release。若是想要本身管理,能够这样:
NSArray* immutableArray = [[NSArray arrayWithArray:mutableArray] retain];
[immutableArray release];
当类中包含其余指针,就要在dealloc函数中手动一一release它们,最后记得[super dealloc]。
- (NSString *)f { NSString *result = [[NSString alloc] initWithFormat:@"Hello"]; return result; }
这样作实际上是会内存泄漏的。alloc方法会建立出来一个string对象,它的retain计数为1。所以该string对象返回时,retain计数为1。在其余对象调用f方法获得string对象后,它通常会retain该string对象(由于调用者认为f返回的应该是一个autorelease对象)。这时,string对象的retain计数变成2。而后调用者在再也不须要stirng对象时,他将会调用release(由于他retain了一次,因此会release一次)。这时string对象的retain计数变成1。正如你所想, string对象没有获得释放。
错误的解决方法:让函数返回前使用[result release];
这样返回的函数对象其实已是空的了。不可行。
正确的解决方法:让函数返回一个autorelease对象。
- (NSString *)f { NSString *result = [[NSString alloc] initWithFormat:@"Hello"]; return [result autorelease]; }
Autorelease实际上只是把对release的调用延迟了,对于每个Autorelease,系统只是把该Object放入了当前的Autorelease pool中,当该pool被释放时,该pool中的全部Object会被调用Release。
autorelease和release没什么区别,只是引用计数减一的时机不一样而已,autorelease会在对象的使用真正结束的时候才作引用计数减一。
函数返回的是一个autorelease对象,而接到它的对象通常须要retain,而后有retain就须要咱们手动release。
若是有AutoreleasePool,那么在内存池管理的范围内的autorelease都不须要咱们手动释放。
自动释放池对象一般以以下模式建立:
[[[Object alloc] init] autorelease];
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init]; A *a = [[A alloc] init]; //引用计数为1 [pool drain]; //引用计数依然为1 [a retain]; //引用计数为2 NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init]; [a autorelease]; //将a添加到pool中,当pool释放的时候,a也被释放 [pool drain]; //引用计数为1 [a release]; //引用计数为0
- (void)f { for(int i = 0; i < 100000; ++i) { //getData返回一个autorelease对象 NSData *data = [self getData]; } //在这里100000个数据对象都还有效 }
因此,autorelease可能致使存在大量临时对象。
解决方法1:在循环中释放对象
- (void)f { for(int i = 0; i < 100000; ++i) {
NSData *data = [[NSData alloc]init]; /* * set data, use data */
[data release]; } //在这里100000个数据对象都被成功释放 }
解决方法2:循环内部建立一个自动释放池
- (void)f { for(int i = 0; i < 100000; ++i) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init]; //getData返回一个autorelease对象 NSData *data = [self getData]; [pool drain]; } //在这里100000个数据对象都被成功释放 }
- (void)setName:(NSString *)newName { name = newName; }
这样写有什么不对的地方呢,当newName在某个地方被release后,该name将失效!
改进后的写法应该以下,以防其余人释放name引用的对象而致使name失效。
- (void)setName:(NSString *)newName { name = newName; [name retain]; }
这样写也有问题,当第二次调用setName的时候,原来的name占的空间并无释放;并且retain以后何时release这个对象?
改进后的写法应该以下
- (void)setName:(NSString *)newName { [name release]; //释放旧值 name = [newName retain]; } - (void)dealloc { [name release]; //对应setName中的retain [super dealloc]; }
但是但是但是,这样仍是有问题,假如本身传值给本身的时候会怎样呢?因此,最正确的应该是
- (void)setName:(NSString *)newName { [newName retain]; //注意,顺序必定是先retain再release。 [name release]; name = newName; } - (void)dealloc { [name release]; //对应setName中的retain [super dealloc]; }
注意,顺序必定是先retain再release。固然还有其余写法,详见《Cocoa® Programming for Mac® OS X》中的内存管理章节,不过我的比较推崇这种写法。
最后的问题是,当newName改变的时候,name也会跟着改变,由于这是浅复制。若是想要让两者独立的话,即深复制,应该这样写
- (void)setName:(NSString *)newName { if (name != newName) //防止复制自身 { [name release]; name = [[NSString alloc] initWithString:newName]; } } - (void)dealloc { [name release]; [super dealloc]; }
1.
NSNumber *myInt = [NSNumber numberWithInteger:100]; //引用计数为1
2.
myInt = [myArr objectAtIndex:0]; [myArr removeObjectAtIndex:0];
此时,myInt引用的对象失效。应当修改成:
myInt = [myArr objectAtIndex:0]; [myInt retain]; [myArr removeObjectAtIndex:0];
3.
NSString *s1 = @"s1"; //引用计数为0xffffffff(不少f就对了) NSString *s2 = [NSString stringWithString:@"s2"]; //引用计数为0xffffffff NSMutableString *s3 = [NSMutableString stringWithString:@"s3"]; //引用计数为1
为何呢,由于s1是常量字符串,s2是使用了常量字符串初始化的不可变字符串对象,都没有引用计数机制。
《Objective-C Beginner's Guide》
《Cocoa® Programming for Mac® OS X》中的内存管理章节
《Objective-C高级编程》中的自动引用计数部分