我知道Cocoa中关于内存管理的基本原则(保留计数,自动释放池等),但是一旦你超越了简单的保留/释放,它就会变得更加混乱.我找不到合适的答案,因为大多数教程都涵盖了简单的场景.我想问一下如何编写代码并避免泄漏的最佳实践.
第一个问题 - 迭代和临时任务:
for (id object in objectArray) { Model *currentItem = object; /* do something with currentItem */ [currentItem release]; }
如果我删除最后一行中的版本,代码将正常工作,但有泄漏.这里的规则是什么?该对象已经存在于objectArray中.我直接分配它,以获得类型.我应该以其他方式这样做吗?这个赋值是否会增加currentItem的retainCount?(它是否类似[[alloc] initWithObject]?)如何知道这个赋值(对象)是否自动释放?
第二个问题 - 即时保留:
Model *model = [unarchiver decodeObjectForKey:@"ARCHIVED_MODEL_OBJECT"]; // it has to be here, because (I was told) unarchiver will return autorelease object [model retain]; label.text = model.data;
有人知道这个特殊的方法是如此有效,我需要立即调用保留返回值,否则我将在下一个任务中遇到null?我在文档中找不到这样的东西.根据保留/释放规则,我希望decodeObjectForKey返回autorelased对象,但它需要一些时间,直到控件返回到app并且池声明要释放的模型对象.对此有什么规定吗?我该如何搜索?
第三个问题 - 自动释放和传递变量:
- (IBAction) loadXMLButtonClicked:(id) sender { objectArray = [self loadData]; // 1 - objectArray is instance var NSArray *objectArray = [self loadData]; // 2 - objectArray is local var // loadXMLButtonClicked is called on button click, here the method finishes // and control goes back to application, autorelease pool is cleaned? // case 1 - objectArray stays retained in instance variable? (because setter was used) // case 2 - objectArray is soon to be released, there were no retains? // (ignore the fact that it's local var, just hypothetically) } - (NSArray *) loadData { NSArray *objectArray = [[NSArray alloc] init]; // populate array here return [objectArray autorelease]; }
第4个问题 - (忍受我,最后一个)解析器xml最佳实践:(我不想使用其他解决方案,使用标准解析器是练习Objective-c内存管理和流程)
基本上这个代码在这里工作,工作良好,没有泄漏,但我真的不知道这是否是正确的方法.我有单独的对象充当解析器,解析XML以收集Model类型的对象数组.现在,解析完成后,我想获得那个数组,虽然我不知道如何(复制数组并释放整个解析器好主意?).请仔细阅读代码并查看评论.
我已多次调试此代码,gdb
用于打印retainCounts,僵尸等等,虽然我可以设法让这个运行并且没有泄漏,但我不知道100%为什么并且想听到一个很好的推理应该如何完成解释.非常感谢.
Controller.m或者
- (NSArray *) loadData { (...) NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; ModelXMLParser *parserDelegate = [[ModelXMLParser alloc] init]; [parser setDelegate:parserDelegate]; [parser parse]; objectArray = [[parserDelegate objectArray] copy]; // is this ok? *i* don't need the parser object so I think I should get rid of it // and copy the data. How this copy works, is it shallow (only new reference to array) // or deep copy (objects allocated again as well)? // how to do deep copy of NSArray? [parserDelegate release]; [parser release]; }
ModelXMLParser.m(简化版)
@implementation ModelXMLParser @synthesize objectArray; // array of objects @synthesize currentObject; // temporary object @synthesize currentChars; // temporary chars @synthesize parseChars; // parse chars only when there's need, leave those /t/n etc - parser didStartElement (...) { if ([elementName isEqualToString:@"objects"]) { objectArray = [[NSMutableArray alloc] init]; } else if ([elementName isEqualToString:@"object"]) { currentObject = [[Model alloc] init]; } else if ([elementName isEqualToString:@"name"]) { // do I have to init currentObject.name (NSString) here? I guess not [self setParseChars:YES]; // just set the flag to make parse control easier } else if ([elementName isEqualToString:@"number"]) { // int isn't object anyway, no init [self setParseChars:YES]; // just set the flag to make parse control easier } } - parser foundCharacters (...) { if (parseChars) { currentChars = [[NSString alloc] initWithString:string]; // why is currentChars retainCount = 2 here? // is it like currentChars = [NSString new] and then currentChars = string? (so retain once more) // is it good way to control parser? (please ignore the NSMutableString and appending example, try this one) // should I just do currentChars = string here? [currentChars autorelease]; // this is currently my solution, because there's no leak, but I feel it's incorrect } } - parser didEndElement (...) { if ([elementName isEqualToString:@"object"]) { [objectArray addObject:[currentObject copy]]; // should I copy here or just addObject, it retains anyway? [currentObject release]; // I've initialized currentObject before, now I don't need it, so I guess retainCount goes to 0 here? } else if ([elementName isEqualToString:@"name"]) { currentObject.name = currentChars; // is this correct, or shoud I do [currentChars copy] as well? [self setParseChars:NO]; [currentChars release]; // as before, initialized, now releasing, but is this really correct? } else if ([elementName isEqualToString:@"number"]) { currentObject.number = [currentChars intValue]; // is this correct, or shoud I do [currentChars copy] as well? [self setParseChars:NO]; [currentChars release]; // as before, initialized, now releasing, but is this really correct? } } - (void) dealloc { // I shouldn't release currentChars or currentObject, those (I suppose) should be freed after parsing done, // as a result of earlier releases? [objectArray release]; [super dealloc]; }
Model.m
@implementation Model @synthesize name; // this is NSString @synthesize number; // this is int - (id) copyWithZone:(NSZone *) zone { Model *copy = [[[self class] allocWithZone:zone] init]; copy.name = [self.name copy]; copy.number = self.number; return copy; } - (void) dealloc { [name release]; // I don't have to release int, right? it's not an object [super dealloc]; }
我对问题4感到特别困惑.对不起,问题可能太久了,但这真的是关于一个话题和对它的深入理解.
由于您不是所有者,因此不应在此处发布对象.如果您是对象,则应该只释放对象.请参阅Cocoa内存管理指南.你只是一个对象的所有者,如果你叫他的名字开始的方法init
,new
或包含copy
在其名称.
由于for循环不使用具有任何这些名称的方法,因此您不是所有者,因此您不得释放这些对象.这将导致在对象完成之前释放对象,这几乎肯定会导致内存损坏和崩溃.
第二个问题 - 即时保留:你不需要立即调用retain,你只需要在自动释放池下一次清空之前调用它.这可能是在您的方法返回主事件循环后不久.由于您不确切知道何时会发生这种情况,因此您必须确保如果您希望能够在函数(loadXMLButtonClicked:
在本例中)返回后能够访问该对象,那么您必须retain
在返回之前使用它.
由于decodeObjectForKey
不首先init
或new
或包含copy
在它的名字,你不会成为一个老板.通话retain
让你成为主人.
首先,使用同名的局部变量遮蔽类成员是不好的做法.其次,除非loadData
被用作多用途实用函数(我猜它不是因为它不带任何参数),它应该直接将结果分配给成员变量objectArray
.返回结果然后将调用函数赋值给成员变量是没有意义且容易出错的.
第三,你没有使用objectArray
属性设置器 - 你只是直接分配给成员变量.如果你想使用setter,你必须明确地说self.objectArray = ...
(self.
在它前面).因此,objectArray
永远不会被保留,因此下次自动释放池清除时,它将被释放,这不是您想要的.你必须在某个时候保留它,或者相反,只是不要在结束时自动释放它loadData
,并为它赋予类成员变量objectArray
.
如果使用属性声明retain
属性,那么使用setter将retain
在分配时自动调用(并且它也release
将是旧值).如果使用属性声明copy
属性,则每次分配属性时都会复制该值,并且您将成为新对象的所有者.
你正在制作一个对象数组的浅表副本.如果要进行深层复制,可以使用该initWithArray:copyItems:
消息.
我必须在这里初始化currentObject.name(NSString)吗?我猜不会?
我不明白这个问题,currentObject.name
附近的代码没有任何提及.
为什么currentChars retainCount = 2?
可能是因为在它的内部初始化过程中,它在retain
某个地方被加了一个额外的时间,但它也几乎肯定是autoreleased
一个额外的时间.如果您遵循Cocoa内存管理指南中的所有规则,您将不会遇到任何问题.您永远不应该依赖保留计数,因为您不知道对象被自动释放了多少次.它们是调试辅助工具,不应该用于程序控制流程.
这是我目前的解决方案,因为没有泄漏,但我觉得这是不正确的?
如果currentChars
下次返回事件循环时不需要使用,那就没关系了.如果您需要使用它,则不应在此处释放或自动释放,然后在您确定已完成时将其释放.
我应该在这里复制还是只是addObject,它还会保留吗?
刚addObject
:当你添加项目到NSArray
,NSSet
或者NSDictionary
,它们会自动retain
将数据结构编.当你删除它们时,它们就是release
d.
其余大部分问题都可以通过遵循规则来回答,或者对我之前已经回答过的一些问题有相同的答案.