Objective-C内存管理,xml解析器和其他非平凡的例子 [英] Objective-C memory management, xml parser and other non-trivial examples

查看:103
本文介绍了Objective-C内存管理,xml解析器和其他非平凡的例子的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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






第一个问题 - 迭代和临时赋值:

  for(objectArray中的id对象){
Model * currentItem = object;
/ *用currentItem做一些事情* /
[currentItem release];
}

如果我删除最后一行中的版本,代码将正常工作,但是有泄漏。
这里有什么规则?该对象已经存在于objectArray中。我直接分配它,以获得类型。我应该以其他方式这样做吗?这个赋值是否会增加currentItem的retainCount? (是否类似于[[alloc] initWithObject]?)如何知道这个赋值(对象)是否自动释放?






第二个问题 - 即时保留:

  Model * model = [unarchiver decodeObjectForKey:@ARCHIVED_MODEL_OBJECT]; 
//它必须在这里,因为(我被告知)unarchiver将返回autorelease对象
[model retain];
label.text = model.data;

如果有人知道这种特殊方法如此有效,我需要立即调用retain返回值,或者我将在下一次任务中遇到空?我在文档中找不到这样的东西。根据保留/释放规则,我希望decodeObjectForKey返回autorelased对象,但是它需要一些时间,直到控件返回到app并且池声明要释放的模型对象。对此有什么规定吗?我应该如何搜索?






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

   - (IBAction)loadXMLButtonClicked:(id)sender {
objectArray = [self loadData]; // 1 - objectArray是实例var
NSArray * objectArray = [self loadData]; // 2 - objectArray是本地var

//在按钮点击时调用loadXMLButtonClicked,此处方法完成
//并且控制返回应用程序,自动释放池被清除?

//案例1 - objectArray保留在实例变量中? (因为使用了setter)
//案例2 - objectArray即将发布,没有保留?
//(忽略它是本地var的事实,只是假设)

}

- (NSArray *)loadData {
NSArray * objectArray = [[NSArray alloc] init];
//在这里填充数组
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];
[解析器解析];

objectArray = [[parserDelegate objectArray] copy];
//这样可以吗? *我*不需要解析器对象所以我想我应该摆脱它
//并复制数据。这个副本是如何工作的,它是浅层的(只是对数组的新引用)
//还是深层复制(同样再次分配对象)?
//如何做NSArray的深层复制?

[parserDelegate release];
[解析器发布];
}

ModelXMLParser.m(简化)

  @implementation ModelXMLParser 

@synthesize objectArray; //对象数组
@synthesize currentObject; //临时对象
@synthesize currentChars; //临时字符
@synthesize parseChars; //仅在需要时解析字符,留下那些/ t / n等

- 解析器didStartElement(...){
if([elementName isEqualToString:@objects]){
objectArray = [[NSMutableArray alloc] init];
}
else if([elementName isEqualToString:@object]){
currentObject = [[Model alloc] init];
}
else if([elementName isEqualToString:@name]){
//我必须在这里初始化currentObject.name(NSString)吗?我猜不是
[self setParseChars:YES]; //只需设置标志以使分析控制更容易
}
else if([elementName isEqualToString:@number]){
// int无论如何都不是对象,没有init
[self setParseChars:YES]; //只需设置标志以使分析控制更容易
}
}

- 解析器foundCharacters(...){
if(parseChars){
currentChars = [[NSString alloc] initWithString:string];
//为什么currentChars retainCount = 2?
//就像currentChars = [NSString new]然后currentChars = string? (所以再次保留)
//这是控制解析器的好方法吗? (请忽略NSMutableString和附加示例,试试这个)
//我应该在这里做currentChars = string吗?

[currentChars autorelease]; //这是我目前的解决方案,因为没有泄漏,但我认为这是不正确的
}
}

- 解析器didEndElement(...){
if ([elementName isEqualToString:@object]){
[objectArray addObject:[currentObject copy]]; //我应该在这里复制还是只是addObject,它还会保留吗?
[currentObject release]; //我以前初始化了currentObject,现在我不需要它,所以我猜这个retainCount会变为0吗?
}
else if([elementName isEqualToString:@name]){
currentObject.name = currentChars; //这是正确的,还是我也会[currentChars copy]?
[self setParseChars:NO];
[currentChars release]; //和以前一样,初始化,现在发布,但这是否正确?
}
else if([elementName isEqualToString:@number]){
currentObject.number = [currentChars intValue]; //这是正确的,还是我也会[currentChars copy]?
[self setParseChars:NO];
[currentChars release]; //和以前一样,初始化,现在发布,但这是否正确?
}
}

- (void)dealloc {
//我不应该释放currentChars或currentObject,解析后应释放那些(我想) ,
//由于早期发布的结果?

[objectArray release];
[super dealloc];
}

Model.m

  @implementation Model 

@synthesize name; //这是NSString
@synthesize数字; //这是int

- (id)copyWithZone:(NSZone *)zone {
Model * copy = [[[self class] allocWithZone:zone] init];
copy.name = [self.name copy];
copy.number = self.number;
返回副本;
}

- (void)dealloc {
[name release];
//我不需要发布int,对吗?它不是一个对象
[super dealloc];
}

我对问题4感到特别困惑。抱歉,问题可能太久了,但这真的是关于一个主题和对它的更深入理解。

解决方案

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



由于您不是所有者,因此不应在此处发布对象。如果您是对象,则应该只释放对象。见内存管理指南可可。如果您调用名称以 init new 开头,或包含<的方法,则您只是对象的所有者code>复制在其名称中。



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



第二个问题 - 即时保留:



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



因为 decodeObjectForKey 不以 init new 开头,或包含 copy 在其名称中,您不会成为所有者。调用 retain 会让你成为拥有者。



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



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



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



如果使用 retain 属性声明属性,则使用setter当您使用它时,它将自动调用 retain (并且它还将 release 旧值)。如果使用 copy 属性声明属性,则每次分配该值时都会复制该值,并且您将成为新对象的所有者。 / p>

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



您正在制作对象数组的浅表副本。如果你想使一个深拷贝,你可以使用的 initWithArray:copyItems: 消息


我必须在这里初始化currentObject.name(NSString)吗?我猜不是吗?


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


为什么currentChars retainCount = 2?


可能是因为在其内部初始化过程中,保留在某个地方加了一段时间,但几乎可以肯定自动释放也是额外的时间。如果您遵循Cocoa内存管理指南中的所有规则,您将不会遇到任何问题。您永远不应该依赖保留计数,因为您不知道对象已被自动释放的次数。它们是调试辅助工具,不应该用于程序控制流程。


这是我目前的解决方案,因为没有泄漏,但我觉得这是不正确的?


如果您不需要使用 currentChars 下次你回到事件循环时,那很好。如果你需要使用它,你不应该在这里发布或自动发布它,然后在你确定你已经完成它时释放它。


我应该在这里复制还是只是addObject,它还会保留吗?


只需 addObject :当您将项目添加到 NSArray NSSet NSDictionary时,它们由数据结构自动保留。当你删除它们时,它们是 release d。



其余大部分问题都可以通过只是按照规则或对我已经回答的一些先前问题给出相同的答案。


I know the basic principles about memory management (retain count, autorelease pools etc) in Cocoa, but once you go past simple retain/release, it's getting a bit more confusing. I couldn't find decent answers for those, as most tutorials cover simple scenarios. I would like to ask about best practices in how to write the code and avoid leaks.


1st question - iterations and temporary assignments:

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

If I remove the release in last line the code will work fine, but there's leak. What's the rule here? The object is already there in objectArray. I'm assigning it directly, to get the type. Should I do this some other way? Is this assignment increasing retainCount of currentItem? (is it something like [[alloc] initWithObject] ?) How to know if this assignment (object) is autoreleased or not?


2nd question - instant retains :

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;

How someone knew that this particular method works so weird, that I need to instantly call retain on returned value, or I will bump into null on next assignment? I couldn't find anything like this in documentation. According to retain/release rules, I would expect that decodeObjectForKey returns autorelased object, but it will take some time until the control goes back to app and pool claims the model object to be released. Is there any rule for that? How should I search for those?


3rd question - autoreleasing and passing variables:

- (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];
}


4th question - (bear with me, last one) parser xml best practice : (I don't want to use other solutions, using standard parser is to practice objective-c memory management and flow)

Basically this code here works, works good and have no leaks, but I don't really know if this is proper approach. I have separate object that acts as parser, parses an XML to collect an array of objects of type Model. Now, after parsing done, externally I would like to obtain that array, though I don't know how (is copying the array and releasing whole parser good idea?). Please run through the code and see the comments.

I've debug this code many times, using gdb for printing retainCounts, zombies, etc. and while I can manage to get this running and without leaks, I don't know 100% why and would like to hear a good reasoning how should this be done with explanation. That would be much appreciated.

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 (simplified)

@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];
}

I'm especially confused with question 4. Sorry for maybe too long question, but this really is about one topic and deeper understanding of it.

解决方案

1st question - iterations and temporary assigments

You should NOT be releasing the objects here since you are not an owner. You should only ever release an object if you are an owner of it. See the Memory Management Guide for Cocoa. You are only an owner of an object if you call a method whose name begins with init, new, or contains copy in its name.

Since the for loop does not use methods with any of those names, you are not an owner, so you MUST NOT release the objects. This will result in freeing objects before they're done, which will almost certainly result in memory corruption and a crash.

2nd question - instant retains:

You don't need to instantly call retain, you just need to call it before the autorelease pool next empties. This will probably be shortly after your method returns to the main event loop. Since you don't know exactly when this will happen, you have to make sure that if you want to be able to access the object after the function (loadXMLButtonClicked: in this case) returns, then you must retain it before you return.

Since decodeObjectForKey does not begin with init or new or contain copy in its name, you are not becoming an owner. Calling retain makes you an owner.

3rd question - autoreleasing and passing variables:

First of all, it's bad practice to shadow a class member with a local variable of the same name. Secondly, unless loadData is being used as a multi-purpose utility function (which I'm guessing it isn't since it doesn't take any parameters), it should just assign the result directly to the member variable objectArray. It's pointless and error-prone to return the result and then have the calling function assign to the member variable.

Thirdly, you're not using the objectArray property setter -- you're just assigning straight to the member variable. If you want to use the setter, you have to explicitly say self.objectArray = ... (with the self. in front of it). Thus, objectArray is never retained, so it's going to be deallocated the next time the autorelease pool clears itself out, which is not what you want. You must retain it at some point, or conversely, just don't autorelease it at the end of loadData, and assign it the class member variable objectArray.

If the property is declared with the retain attribute, then using the setter will automatically call retain when you assign with it (and it will also release the old value). If instead the property is declared with the copy attribute, then the value will instead be copied every time you assign to it, and you will become an owner of the new object.

4th question - parser xml best practice:

You're making a shallow copy of the object array. If you want to make a deep copy, you can use the initWithArray:copyItems: message.

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

I don't understand this question, there's no currentObject.name mentioned at all anywhere nearby that code.

why is currentChars retainCount = 2 here?

Probably because during its internal initialization process, it was retained an extra time somewhere, but it was also almost certainly autoreleased an extra time as well. If you follow all of the rules from the Memory Management Guide for Cocoa, you won't have any problems. You should never rely on retain counts, since you don't know how many times an object has been autoreleased. They're a debugging aid, not something that should be used for program control flow.

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

If you won't need to use currentChars the next time you return to the event loop, then it's fine. If you will need to use it, you should not release or autorelease it here, and then release it when you're sure you're done with it.

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

Just addObject: when you add items into an NSArray, NSSet, or NSDictionary, they are automatically retained by the data structure. When you remove them, they're released.

Most of the rest of the questions can be answered by just following the rules or have identical answers to some of the previous questions which I've already answered.

这篇关于Objective-C内存管理,xml解析器和其他非平凡的例子的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆