简体   繁体   English

iPad应用中的内存泄漏

[英]memory leak in iPad app

I've been working a product display app but it has a memory leak that causes it to crash after too many categories have been loaded up. 我一直在使用产品展示应用程序,但是它存在内存泄漏,导致在加载了太多类别后崩溃。 The app works via a SplitViewController that lists the categories down the left and, once tapped, the product images show in the detailViewController on the right. 该应用程序通过SplitViewController进行工作,该SplitViewController在左下方列出类别,一旦点击,产品图像就会在右侧的detailViewController中显示。 Selecting category after category eventually crashes the app. 逐个选择一个类别最终会使应用程序崩溃。

I've used the instruments -> Leaks tool to trace the problem and I get told that NSString appendString is a leak. 我使用了工具-> Leaks工具来跟踪问题,并被告知NSString appendString是一个泄漏。 The number of strings leaked seems to match the number of products in the category selected so I'm guessing one of my loops holds the problem but, after playing around with AutoreleasePools I haven't solved it yet. 泄漏的字符串数量似乎与所选类别中的产品数量匹配,因此我猜我的一个循环存在问题,但是在使用AutoreleasePools之后,我尚未解决。

My code: This method is called when the category is selected and parses an XML document 我的代码:选择类别并解析XML文档时将调用此方法

- (NSMutableArray*) processXML{
//NSAutoreleasePool *pool4  = [[NSAutoreleasePool alloc] init];
// Initialize the productEntries MutableArray declared in the header
products = [[NSMutableArray alloc] init];   
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSMutableString *documentsDirectory = [[NSMutableString stringWithFormat:@"%@", [paths objectAtIndex: 0]] autorelease];
// paths to save inputs to
NSString *productsFile = [documentsDirectory stringByAppendingFormat: @"/products2.xml"];
NSData *data = [NSData dataWithContentsOfFile: productsFile];

// Create a new rssParser object based on the TouchXML "CXMLDocument" class, this is the object that actually grabs and processes the RSS data
NSError *error = nil;
CXMLDocument *rssParser = [[[CXMLDocument alloc] initWithData:(NSData *)data encoding:NSUTF8StringEncoding options:0 error:&error] autorelease];  

// Create a new Array object to be used with the looping of the results from the        rssParser
NSArray *resultNodes = NULL;

//NSString *xPathStart, *xPathEnd, *category, *finalStr;
NSString *xPathStart = [[NSString stringWithFormat:@""] autorelease];
NSString *xPathEnd = [[NSString stringWithFormat:@""] autorelease];
NSString *category = [[NSString stringWithFormat:@""] autorelease];
NSString *finalStr = [[NSString stringWithFormat:@""] autorelease];
NSString *detailStr = [[NSString stringWithFormat: detailItem] autorelease];
// category to be parsed - build up xPath expression
if([detailStr isEqualToString: @"On Order Stock"]) {
    xPathStart = @"/products/product[instock='2";
    xPathEnd = @"']";
    finalStr = [NSString stringWithFormat:@"%@%@", xPathStart, xPathEnd];

} else {
    xPathStart = @"/products/product[category='";
    category = detailItem;
    xPathEnd = @"']";
    finalStr = [NSString stringWithFormat:@"%@%@%@", xPathStart, category, xPathEnd];
}
resultNodes = [rssParser nodesForXPath: finalStr error:nil];


// Loop through the resultNodes to access each items actual data
for (CXMLElement *resultElement in resultNodes) {

    Product *productItem = [[Product alloc]  init];
    [productItem setCode: [[[resultElement childAtIndex: 1] stringValue] autorelease]];
    [productItem setImage: [[[resultElement childAtIndex: 5] stringValue] autorelease]];

    // Add the product object to the global productEntries Array so that the view can access it.
    [products addObject: productItem];

    [productItem release];
}
//[pool4 release];
return products;

} }

As you can see I went a little crazy with autoReealse on my strings. 如您所见,我对字符串使用autoReealse有点疯狂。 The other code segment that displays the images could be the problem although Leaks does mention processXML directly. 尽管Leaks确实直接提到了processXML,但显示图像的其他代码段可能是问题。

- (void) displayImages:(NSMutableArray *)anArray {

// create scrollView object
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 100)];
scrollView.pagingEnabled = NO;
scrollView.scrollEnabled = YES;
scrollView.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1];
scrollView.userInteractionEnabled = YES;

//create info area below scrollView
infoView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 100,  self.view.frame.size.width, 100)];
[infoView setContentSize:CGSizeMake(self.view.frame.size.width, 100)];
infoView.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1];
infoView.scrollEnabled = NO;

[barcodeImgView setImage:[UIImage imageNamed:@"barcode2.jpg"]];
[infoView addSubview:codeLbl];
[infoView addSubview:nameLbl];
[infoView addSubview:priceLbl];
[infoView addSubview:dimensionsLbl];
[infoView addSubview:stockLbl];
[infoView addSubview:commentsLbl];
[infoView addSubview:barcodeImgView];
infoView.userInteractionEnabled = YES;

[codeLbl setText:[[NSString stringWithFormat:@""] autorelease]];
[nameLbl setText:[[NSString stringWithFormat:@""] autorelease]];
[priceLbl setText:[[NSString stringWithFormat:@""] autorelease]];
[commentsLbl setText:[[NSString stringWithFormat:@""] autorelease]];
[stockLbl setText:[[NSString stringWithFormat:@""] autorelease]];
[dimensionsLbl setText:[[NSString stringWithFormat:@""] autorelease]];

// hold x and y of each image
int x = 30;
int y = 50;
int noOfImages = [anArray count];
int maxRowWidth = (noOfImages / 3) + 1;
int xcount = 0; // position across the row, reset to zero and drop image down when equal to (noOfImages / 3) + 1

//NSAutoreleasePool *displayPool = [[NSAutoreleasePool alloc] init];

for(int i = 0; i < noOfImages; i++) {

    // declare Product object to hold items in anArray
    Product *prod = [[Product alloc] init];
    prod = [anArray objectAtIndex: i];
    // try for image in Documents folder, later checks it exists and if not uses Resource location
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSMutableString *documentsDirectory = [[NSString stringWithFormat:@"%@", [paths objectAtIndex: 0]] autorelease];;

    // paths to save inputs to
    NSString *imgName = [[NSString stringWithFormat:@"%@/%@", documentsDirectory, [prod image]] autorelease];
    NSString *productName = [[NSString stringWithFormat:@"%@", [prod code]] autorelease];
    // create and size image
    UIImage *image = [UIImage imageWithContentsOfFile: imgName];

    // set up button
    UIButton *button= [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [button addTarget:self action:@selector(imageButtonClick:) forControlEvents:(UIControlEvents)UIControlEventTouchDown];
    [button setTitle:productName forState:UIControlStateNormal];
    button.titleLabel.font = [UIFont systemFontOfSize: 0];
    [button setTitleColor: [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1] forState: UIControlStateNormal];

    CGSize imageSize = image.size;
    CGFloat height = imageSize.height;
    CGFloat width = imageSize.width;
    CGFloat ratio = 160 / width; // get ratio to divide height by
    UIGraphicsBeginImageContext(CGSizeMake((height * ratio),160));  
    CGContextRef context = UIGraphicsGetCurrentContext();
    [image drawInRect: CGRectMake(0, 0, height * ratio, 160)];  
    UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    // create frame for image
    CGRect newFrame = CGRectMake(x, y, 160,160);
    UILabel *codeLabel = [[UILabel alloc] initWithFrame:CGRectMake(x, y - 20, 170, 20)];
    codeLabel.text = productName;
    codeLabel.textColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1];
    codeLabel.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1];
    [button setFrame: newFrame];
    [button setBackgroundImage:smallImage forState:UIControlStateNormal];
    [scrollView setContentSize:CGSizeMake((maxRowWidth * 160) + 160,self.view.frame.size.height - 100)];
    [self.scrollView addSubview:button];
    [self.scrollView addSubview:codeLabel];


    xcount++;
    x = x + 170; // move across the page
    if(xcount == maxRowWidth) {
        y = y + 210;  // move down the screen for the next row
        x = 30;   // reset x to left of screen
        xcount = 0;   // reset xcount;
    }

    [prod release];
}
//[displayPool release];
[self.view addSubview: scrollView];
[self.view addSubview: infoView];
[scrollView release];
[infoView release];

[pool release];

} }

By the way, pool is an autoreleasePool defined in the h file for the class. 顺便说一句,pool是在h文件中为该类定义的autoreleasePool。

I would really appreciate any specific help regarding my code or general tips on what could be wrong. 对于我的代码的任何特定帮助或有关可能出问题的常规提示,我将非常感谢。

I see a few things wrong: 我发现一些错误:

  1. As was mentioned in the comments, you're abusing -autorelease in ways which make grown men cry and that which will crash your app. 正如评论中提到的那样,您滥用-autorelease的方式会使成年男子哭泣,而这会使您的应用程序崩溃。
  2. -processXML is returning an owned object. -processXML返回一个拥有的对象。 You're allocating products and returning it. 您正在分配products并退货。 This breaks convention, because the method name does not begin with new or alloc , and does not contain copy . 这违反了约定,因为方法名不是以newalloc开头,并且不包含copy You should return [products autorelease]; 您应该return [products autorelease]; instead. 代替。 However, even that is shady, because since products isn't declared locally, it's probably an instance variable. 但是,即使这样也很阴暗,因为由于products不是在本地声明的,因此它可能是一个实例变量。 In that case, what happens if processXML gets invoked multiple times? 在这种情况下,如果processXML调用processXML会发生什么? You have an owned object referenced by the instance variable, and suddenly you overwrite that reference with a new one... = memory leak. 您有一个由实例变量引用的拥有对象,然后突然用新的对象覆盖了该引用... =内存泄漏。
  3. Every time someone does MyClass * object = [[MyClass alloc] init]; object = [something thatReturnsAMyClass]; 每次有人做MyClass * object = [[MyClass alloc] init]; object = [something thatReturnsAMyClass]; MyClass * object = [[MyClass alloc] init]; object = [something thatReturnsAMyClass]; , a kitten dies. ,一只小猫死了。 If you then do [object release]; 如果是,则执行[object release]; , a second dies for good measure. ,第二死是有道理的。 This is a terrible, terrible memory leak (and a likely crash). 这是一个可怕的,可怕的内存泄漏(并且很可能崩溃)。 You're allocating a new object and then immediately throwing it away but never releasing it. 您正在分配一个新对象,然后立即将其丢弃,但从不释放它。 That you do this suggests you don't really get what a pointer is. 这样做表明您并没有真正获得指针。 I suggest reading " Everything you need to know about pointers in C " 我建议阅读“ 您需要了解的有关C指针的所有内容
  4. On a lighter note, you should check out -[NSString stringByAppendingPathComponent:] . 简单一点,您应该签出-[NSString stringByAppendingPathComponent:] NSString has a bunch of really nice methods for dealing with paths. NSString有很多非常好的处理路径的方法。

I hope I don't come off as too harsh. 我希望我不要太苛刻。 :) :)

Some while ago at another post somebody said that one should read about the memory management and I was actually thinking that this answer is not really right. 前不久在另一篇文章中,有人说应该阅读有关内存管理的内容,而我实际上是在想这个答案并不正确。 What is wrong with some trial and error and learning by doing. 反复试验和边做边学怎么了? But after I had my painful experiences with memory I must admit that this guy was right. 但是在经历了痛苦的记忆经历后,我必须承认这个家伙是对的。 Take the time. 慢慢来。 Go and read the chapter on memory management in the apple documentation. 请去阅读Apple文档中有关内存管理的章节。

As above stated already you should not autorelease an object you do not own. 如上所述,您不应该自动释放您不拥有的对象。 But this might not cause the trouble. 但这可能不会造成麻烦。 You can next to the instruments use Build+Analyze in the Build menu. 您可以在“工具”旁边使用“构建”菜单中的“构建+分析”。 This will help you to find out more. 这将帮助您了解更多信息。

Basically you need to release objects you create which you own (which one you own is in the documentation, basically those created with "alloc" and some more). 基本上,您需要释放自己创建的对象(文档中的对象,基本上是用“ alloc”创建的对象)。 If you cannot release them you assign them to the autorelease pool. 如果无法释放它们,则将它们分配给自动释放池。 This is the case with the "products" you return from processXML. 从processXML返回的“产品”就是这种情况。 When is the autorelease pool drained? 自动释放池何时耗尽? This is when the next time the framework of the application is back in control (I think it was called run-loop or something). 这是下一次应用程序框架重新处于控制状态时(我认为它被称为运行循环等)。 This can be a while and so you should not open to much objects which are assigned to an autorelease pool. 这可能需要一段时间,因此您不应打开分配给自动释放池的太多对象。

So to help you really read that chapter: memory management programming guide 因此,可以帮助您真正阅读该章: 内存管理编程指南

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM