2010-12-13 3 views
0

Я работаю с программным продуктом, но у него есть утечка памяти, из-за которой он сбой после того, как загружено слишком много категорий. Приложение работает через SplitViewController, в котором перечислены категории слева и, после использования, изображения продукта отображаются в detailViewController справа. Выбор категории после категории в конечном итоге приводит к сбою приложения.утечка памяти в приложении для iPad

Я использовал инструменты -> Инструмент утечки, чтобы проследить проблему, и мне сказали, что 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]; 

}

Кстати, бассейн является autoreleasePool определено в ч файла для данного класса.

Я бы очень признателен за любую конкретную помощь в отношении моего кода или общих советов о том, что может быть неправильным.

+4

'. [[NSString stringWithFormat: @ ""] autorelease];' *** НЕ ДЕЛАТЬ ЭТО *** Вы overreleasing эти объекты и они будут * разбивать вашу программу *. –

+0

Ваш код не включает вызовы 'appendString:', которые, по вашему утверждению, идентифицируются как источник утечки. Кроме того, это не вызовет утечку, но все эти строки '[[NSString stringWithFormat: whatever] autorelease]' абсолютно ошибочны и, скорее всего, вызовут сбой. Вы не владеете строкой, поэтому ее не следует выпускать. http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html – Chuck

ответ

2

Я вижу несколько вещей неправильно:

  1. Как было отмечено в комментариях, вы злоупотребляя -autorelease способами, которые делают взрослые мужчины плачут и то, что приведет к сбою приложения.
  2. -processXML возвращает принадлежащий ему объект. Вы назначаете products и возвращаете его. Это нарушает соглашение, поскольку имя метода не начинается с new или alloc и не содержит 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 имеет кучу действительно хороших методов борьбы с путями.

Надеюсь, я не оторвусь как слишком суровый. :)

+0

Никогда не слишком суровый, Дейв. Я знал, что я злоупотребляю autorelease, и это противоречило тому, что я прочитал в документации, но не смог отследить утечку appendString, от которой я отчаялся. Я изменил объект продуктов, чтобы исправить его. Все еще охота за этими appendString. – Steve

1

Некоторое время назад в другом посте кто-то сказал, что нужно прочитать об управлении памятью, и я действительно думал, что этот ответ не совсем прав. Что не так с некоторыми пробными ошибками и обучением.Но после того, как у меня были болезненные переживания с памятью, я должен признать, что этот парень был прав. Не торопитесь. Перейдите и прочитайте главу об управлении памятью в документации Apple.

Как уже указано выше, вы не должны авторизовать объект, который у вас нет. Но это может не вызвать проблемы. Вы можете рядом с инструментами использовать Build + Analyze в меню Build. Это поможет вам узнать больше.

В принципе вам необходимо освободить объекты, которые вы создаете, которыми вы владеете (какой из них у вас есть, в документации, в основном созданной с помощью «alloc» и еще нескольких). Если вы не можете их освободить, вы назначаете их в пул автозапуска. Это относится к «продуктам», которые вы возвращаете из processXML. Когда сливается автореферат? Это когда в следующий раз рамка приложения вернется в управление (я думаю, что это называется run-loop или что-то еще). Это может произойти некоторое время, поэтому вам не следует открывать много объектов, которые назначаются пулу автозаполнения.

Таким образом, чтобы помочь вам действительно прочитать эту главу: memory management programming guide

Смежные вопросы