iphone - Objective-C 内存管理、xml 解析器和其他重要示例

标签 iphone objective-c memory

我知道 Cocoa 中关于内存管理(保留计数、自动释放池等)的基本原则,但是一旦你通过简单的保留/释放,它就会变得更加困惑。我找不到合适的答案,因为大多数教程都涵盖了简单的场景。我想问一下如何编写代码和避免泄漏的最佳实践。

第一个问题 - 迭代和临时分配:

for (id object in objectArray) {    
     Model *currentItem = object;
    /* do something with currentItem */
    [currentItem release];
}

如果我删除最后一行中的发布,代码将正常工作,但存在泄漏。
这里的规则是什么?该对象已存在于 objectArray 中。我直接分配它,以获取类型。我应该以其他方式这样做吗?此分配是否增加了 currentItem 的保留计数? (它类似于 [[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 对象,但控制返回到应用程序和池声明要释放的模型对象需要一些时间。有什么规定吗?我应该如何搜索这些?

第三个问题 - 自动释放和传递变量:
- (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 以收集模型类型的对象数组。现在,在解析完成后,我想从外部获取该数组,尽管我不知道如何(复制数组并释放整个解析器是个好主意?)。请运行代码并查看注释。

我已经多次调试此代码,使用 gdb用于打印retainCounts、zombies 等,虽然我可以设法让它运行并且没有泄漏,但我不知道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];
}

型号.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 尤其感到困惑。抱歉,这个问题可能太长了,但这确实是关于一个主题和对它的更深入理解。

最佳答案

第一个问题 - 迭代和临时分配

您不应该在此处释放对象,因为您不是所有者。如果您是对象的所有者,您应该只释放它。见 Memory Management Guide for Cocoa .如果您调用名称以 init 开头的方法,您只是对象的所有者。 , new ,或包含 copy以其名义。

由于 for 循环不使用具有任何这些名称的方法,因此您不是所有者,因此您不得释放对象。这将导致在对象完成之前释放对象,这几乎肯定会导致内存损坏和崩溃。

第二个问题 - 即时保留:

你不需要立即调用保留,你只需要在自动释放池下一次清空之前调用它。这可能是在您的方法返回主事件循环后不久。由于您不知道这会在何时发生,您必须确保如果您希望能够在函数(在本例中为 loadXMLButtonClicked:)返回后访问该对象,那么您必须 retain在你回来之前。

decodeObjectForKey不以 init 开头或 new或包含 copy以它的名义,您不会成为所有者。调用 retain使您成为所有者。

第三个问题 - 自动释放和传递变量:

首先,用同名的局部变量隐藏类成员是不好的做法。其次,除非loadData被用作多用途实用程序函数(我猜它不是,因为它不带任何参数),它应该只将结果直接分配给成员变量 objectArray .返回结果然后将调用函数分配给成员变量是毫无意义且容易出错的。

第三,您没有使用 objectArray属性 setter ——您只是直接分配给成员变量。如果要使用setter,必须明确说self.objectArray = ... (前面有 self.)。因此,objectArray永远不会保留,所以它会在下一次自动释放池清除时被释放,这不是你想要的。你必须在某个时候保留它,或者相反,只是不要在 loadData 结束时自动释放它。 ,并为其分配类成员变量 objectArray .

如果属性是用 retain 声明的属性,然后使用setter 会自动调用retain当你用它赋值时(它也会 release 旧值)。如果相反,该属性是用 copy 声明的属性,那么每次分配给它时都会复制该值,并且您将成为新对象的所有者。

第 4 个问题 - 解析器 xml 最佳实践:

您正在制作对象数组的浅拷贝。如果要进行深拷贝,可以使用 initWithArray:copyItems: 信息。

do i have to init currentObject.name (NSString) here? i guess not?



我不明白这个问题,没有currentObject.name在该代码附近的任何地方都提到过。

why is currentChars retainCount = 2 here?



可能是因为在其内部初始化过程中,是retain在某处多写了一些时间,但几乎可以肯定是 autoreleased还有额外的时间。如果您遵循 Cocoa 内存管理指南中的所有规则,您就不会有任何问题。你永远不应该依赖保留计数,因为你不知道一个对象被自动释放了多少次。它们是调试辅助工具,而不是应该用于程序控制流的东西。

this is currently my solution, because there's no leak, but i feel it's incorrect?



如果您不需要使用 currentChars下次您返回事件循环时,就可以了。如果你需要使用它,你不应该在这里释放或自动释放它,然后在你确定你完成它时释放它。

should I copy here or just addObject, it retains anyway?



刚刚addObject :当您将项目添加到 NSArray 中时, NSSet , 或 NSDictionary ,它们会自动 retain由数据结构编辑。当您删除它们时,它们是 release d.

其余的大部分问题都可以通过遵循规则来回答,或者对我已经回答过的一些之前的问题有相同的答案。

关于iphone - Objective-C 内存管理、xml 解析器和其他重要示例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/546588/

相关文章:

iphone - 自定义 uiscrollview 不滚动(使用 coretext)

iphone - UIAlertView 弹窗之间的 UIGestureRecognizer 事件

iphone - 在 iPhone 中获取地址信息

c++ - 范围结束从内存中清除变量?

ios - Swift:要求用户在单击每个停止按钮后输入文本文件名

ios - 128 位 AES 加密在 Objective-C 中返回 null

ios - 如何更改选中和未选中的选项卡栏图像颜色

iphone - 如何在 iPhone 上制作无限长的 ScrollView ?

c - 为启动时运行的 linux 内核添加代码

C++ 视口(viewport)缓存目录中的文件