在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
本文记录Objective-C在内存管理方面的一些注意点。另有一篇转载的未公开笔记——Objective-C内存管理机制学习笔记【转】。 ARC 与 MRCiOS 有两种内存管理机制:ARC 与 MRC。 ARC:Automatic Reference Counting,自动引用计数 MRC:Manual Reference Counting,手动引用计数
ARC 的原理:编译器在编译期间在代码里自动插入 retain/release
ARC 禁止手动调用 retain/release/retainCount/dealloc
引用计数在引用计数中,每一个对象负责维护对象所有引用的计数值。 创建对象后,该对象的引用计数值为1。一旦引用计数的值为0,则对象就会自动dealloc。 包含alloc/new/copy/mutableCopy的方法 引用计数+1 retain 引用计数+1 release 引用计数-1 对于每个 retain,一定要对应一个 release 或一个 autorelease。 查看方法:[object retainCount];
内存管理的思考方式
对 “自己” 一词的理解,可以解释” 对象的使用环境 “,或者理解为程序猿 “本身”
// 1. 自己生成的对象,自己持有 // 自己生成并持有对象,此时对象的引用计数为 1 id obj = [NSObject alloc] init]; // 2. 非自己生成的对象,自己也能持有 // 取得非自己生成并持有的对象,此时 obj 是经过 autorelease 的 id obj = [NSMutableArray array]; // 然后持有 [obj retain]; // 3. 自己持有的对象需要自己释放 // 自己生成并持有对象 id obj = [NSObject alloc] init]; // 自己释放 [obj release]; // 4. 非自己持有的对象无法释放 // 自己生成并持有对象,此时对象的引用计数为 1 id obj = [NSObject alloc] init]; // 释放,此时引用计数为 0 [obj release]; // 再次释放,会引发 Crash [obj release];
添加对象到Array时记得release1. obj对象创建后, 计数为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,内存不泄漏*/
NSArrayNSArray* 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];
系统自带的绝大多数类方法返回的对象,都是经过 autorelease 的,比如 [NSArray array]、[NSNumber numberWithInt] dealloc负责本类属性的释放及调用父类的dealloc当类中包含其他指针,就要在dealloc函数中手动一一release它们,最后记得[super dealloc]。
让函数返回一个autorelease对象- (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]; }
AutoreleaseAutorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的Autorelease pool中,当该pool被释放时,该pool中的所有Object会被调用Release。 函数返回的是一个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
autorelease可能导致存在大量临时对象- (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) { 解决方法2:循环内部创建一个自动释放池 - (void)f { for(int i = 0; i < 100000; ++i) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init]; //getData返回一个autorelease对象 NSData *data = [self getData]; [pool drain]; } //在这里100000个数据对象都被成功释放 }
setter中的内存管理- (void)setName:(NSString *)newName { name = newName; } 这样写有什么不对的地方呢,当newName在某个地方被release后,该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高级编程》中的自动引用计数部分 |
请发表评论