我知道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;
有人知道这个特殊的方法有多么奇怪,我需要立即调用retain返回值,否则我将在下一个赋值时遇到null?我在文档中找不到这样的东西。根据保留/释放规则,我希望decodeObjectForKey返回autorelased对象,但它需要一些时间,直到控件返回到app并且池声明要释放的模型对象。对此有什么规定吗?我该如何搜索?
第3个问题 - 自动释放和传递变量:
- (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感到特别困惑。很抱歉也许这个问题太长了,但这真的是关于一个话题和对它的深入理解。
答案 0 :(得分:7)
由于您不是所有者,因此不应在此处发布对象。如果您是对象,则应该只释放对象。请参阅Memory Management Guide for Cocoa。如果您调用名称以init
,new
开头或名称中包含copy
的方法,则您只是对象的所有者。
由于for循环不使用具有任何这些名称的方法,因此您不是所有者,因此您不得释放这些对象。这将导致在对象完成之前释放它们,这几乎肯定会导致内存损坏和崩溃。
您不需要立即调用retain,您只需在自动释放池下一次清空之前调用它。这可能是在您的方法返回主事件循环后不久。由于您不确切知道何时会发生这种情况,因此您必须确保如果您希望能够在函数(在这种情况下为loadXMLButtonClicked:
)返回后访问该对象,那么您必须{{1}在你回来之前它。
由于retain
不以decodeObjectForKey
或init
开头或其名称中包含new
,因此您不会成为所有者。致电copy
会让您成为拥有者。
首先,使用同名的局部变量遮蔽类成员是不好的做法。其次,除非retain
被用作多用途实用函数(我猜它不是因为它不带任何参数),它应该只是将结果直接赋给成员变量{ {1}}。返回结果然后将调用函数赋值给成员变量是没有意义且容易出错的。
第三,你没有使用loadData
属性设置器 - 你只是直接分配给成员变量。如果您想使用setter,则必须明确说出objectArray
(前面有objectArray
)。因此,self.objectArray = ...
永远不会被保留,因此下次自动释放池自行清除时将会释放它,这不是您想要的。您必须在某个时刻保留它,或者相反,只是不要在self.
的末尾自动释放它,并为其分配类成员变量objectArray
。
如果使用loadData
属性声明属性,那么使用setter会在分配时自动调用objectArray
(并且它也将retain
旧值。如果使用retain
属性声明属性,则每次分配该值时都会复制该值,并且您将成为新对象的所有者。
您正在制作对象数组的浅表副本。如果要进行深层复制,可以使用initWithArray:copyItems:
消息。
我必须在这里初始化currentObject.name(NSString)吗?我猜不是吗?
我不明白这个问题,在该代码附近的任何地方都没有提到release
。
为什么currentChars retainCount = 2?
可能是因为在其内部初始化过程中,copy
在某个地方加了一个额外的时间,但也几乎肯定currentObject.name
也是一个额外的时间。如果您遵循Cocoa内存管理指南中的所有规则,您将不会遇到任何问题。您永远不应该依赖保留计数,因为您不知道对象被自动释放了多少次。它们是一种调试辅助工具,不应该用于程序控制流程。
这是我目前的解决方案,因为没有泄漏,但我觉得这是不正确的?
如果下次返回事件循环时不需要使用retain
,那就没关系了。如果你需要使用它,你不应该在这里发布或自动发布它,然后在你确定你已经完成它的时候发布它。
我应该在这里复制还是只是addObject,它还会保留吗?
仅autoreleased
:当您将项目添加到currentChars
,addObject
或NSArray
时,数据结构会自动NSSet
。当您删除它们时,它们是NSDictionary
d。
其余大部分问题都可以通过遵循规则来回答,或者对我之前已经回答过的一些问题有相同的答案。
答案 1 :(得分:1)
@ 1st question
您只能获得objectArray中对象的引用。它仍然在objectArray中,它也保留了对象并释放它在这里并不好,因为你没有做任何保留它的东西。
请参阅此处了解一些rules
答案 2 :(得分:0)
@ 2nd question
看起来您正在设置UILabel文本属性,在这种情况下使用 copy 。在文档中说:
@property(nonatomic, copy) NSString *text;
这意味着Label将复制并保留该副本,而不会更改或保留用于分配属性的对象。
答案 3 :(得分:0)
@ 3rd question
“objectArray = [self loadData]; // 1 - objectArray is instance var
”行实际上不是一个setter,因为它直接访问实例变量。要使用setter,需要通过 self
self.objectArray = [self loadData];
...如果您的属性被声明为(nonatomic, copy)
,旧的objectArray将被释放,并且将创建一个带有副本的新属性,从而被保留。
答案 4 :(得分:0)
答案 5 :(得分:0)
静态分析器
除了Memory Management Programming Guide for Cocoa 之外,静态分析器是一个不可或缺的工具。
项目 - >项目设置 - >构建 - >构建选项 - >运行静态分析器
确保勾选。
它会告诉您正在执行的所有内存分配错误。因此,您将更好地了解如何创建对象,双重自动释放错误,双重释放错误,引用已发布的对象等。
我多次阅读内存管理原则但直到我使用静态分析器才得到它。
现在我在这方面做得更好,大部分时间都是正确的。然而,静态分析器仍然是必不可少的,因为它指出了错误和遗漏错误。
洋一