【问题标题】:memory leak in iPad appiPad应用程序中的内存泄漏
【发布时间】:2010-12-13 21:15:56
【问题描述】:

我一直在使用产品展示应用程序,但它存在内存泄漏,导致加载过多类别后崩溃。该应用程序通过 SplitViewController 工作,该控制器在左侧列出类别,一旦点击,产品图像就会显示在右侧的 detailViewController 中。 选择一个又一个类别最终会使应用程序崩溃。

我使用了instruments -> Leaks 工具来追踪问题,我被告知NSString appendString 是一个泄漏。泄漏的字符串数量似乎与所选类别中的产品数量相匹配,所以我猜我的一个循环存在问题,但是在使用 AutoreleasePools 之后我还没有解决它。

我的代码: 选择类别并解析 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;

}

如您所见,我对我的琴弦上的 autoReealse 有点疯狂。显示图像的其他代码段可能是问题所在,尽管 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];

}

顺便说一下,pool 是在 h 文件中为类定义的 autoreleasePool。

非常感谢有关我的代码的任何具体帮助或关于可能出错的一般提示。

【问题讨论】:

  • [[NSString stringWithFormat:@""] autorelease]; 不要这样做。您过度释放这些对象,它们会使您的程序崩溃 .
  • 您的代码不包含对appendString: 的任何调用,您声称这是泄漏源。此外,这不会导致泄漏,但所有这些 [[NSString stringWithFormat:whatever] autorelease] 行都是绝对错误的,很可能会导致崩溃。您不拥有该字符串,因此您不能释放它。 developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…

标签: objective-c ipad memory-leaks


【解决方案1】:

我看到了一些错误:

  1. 正如 cmets 中所提到的,您滥用 -autorelease 的方式会让成年男子哭泣,这会导致您的应用崩溃。
  2. -processXML 正在返回一个拥有的对象。您正在分配 products 并返回它。这打破了约定,因为方法名称不以newalloc 开头,并且不包含copy。您应该改为return [products autorelease];。然而,即使这样也很可疑,因为products 没有在本地声明,它可能是一个实例变量。在这种情况下,如果processXML 被多次调用会发生什么?你有一个实例变量引用的拥有对象,突然你用一个新的引用覆盖了那个引用...... = 内存泄漏。
  3. 每当有人这样做MyClass * object = [[MyClass alloc] init]; object = [something thatReturnsAMyClass];,一只小猫就会死去。如果你然后做[object release];,第二个死了。这是一个可怕的、可怕的内存泄漏(并且可能会崩溃)。您正在分配一个新对象,然后立即将其丢弃但从未释放它。你这样做表明你并没有真正理解指针是什么。我建议阅读“Everything you need to know about pointers in C
  4. 简单来说,您应该查看-[NSString stringByAppendingPathComponent:]NSString 有很多非常好的处理路径的方法。

我希望我不会表现得太严厉。 :)

【讨论】:

  • 永远不要太苛刻,戴夫。我知道我在滥用自动释放,它确实与我在文档中阅读的内容背道而驰,但无法追踪到 appendString 泄漏,我感到绝望。我已经改变了 products 对象来修复它的那一面。仍在寻找这些 appendString。
【解决方案2】:

前段时间在另一篇文章中,有人说应该阅读有关内存管理的内容,我实际上认为这个答案并不正确。一些试错和边做边学有什么问题。但在我经历过痛苦的记忆之后,我必须承认这个人是对的。慢慢来。去阅读苹果文档中关于内存管理的章节。

如上所述,您不应该自动释放您不拥有的对象。但这可能不会引起麻烦。您可以在仪器旁边使用 Build 菜单中的 Build+Analyze。这将帮助您了解更多信息。

基本上你需要释放你创建的你拥有的对象(你拥有的对象在文档中,基本上是那些使用“alloc”创建的对象等等)。如果您无法释放它们,则将它们分配给自动释放池。从 processXML 返回的“产品”就是这种情况。自动释放池何时耗尽?这是下一次应用程序的框架重新控制时(我认为它被称为运行循环或其他东西)。这可能需要一段时间,因此您不应打开太多分配给自动释放池的对象。

所以为了帮助你真正阅读那一章:memory management programming guide

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-28
    • 2010-11-27
    • 1970-01-01
    相关资源
    最近更新 更多