утечка памяти в приложении iPad - PullRequest
0 голосов
/ 14 декабря 2010

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

Я использовал инструменты -> инструмент «Утечки», чтобы отследить проблему, и мне сказали, что NSString appendString - утечка.Количество пропущенных строк, кажется, соответствует количеству продуктов в выбранной категории, поэтому я предполагаю, что один из моих циклов содержит проблему, но, поиграв с AutoreleasePools, я еще не решил ее.

Мойcode: этот метод вызывается, когда категория выбирается и анализирует 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, определенный в h-файле длякласс.

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

Ответы [ 2 ]

2 голосов
/ 14 декабря 2010

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

  1. Как уже упоминалось в комментариях, вы злоупотребляете -autorelease способами, которые заставляют взрослых людей плакать, а также приводят к краху вашего приложения.
  2. -processXML возвращает принадлежащий объект.Вы выделяете products и возвращаете его.Это нарушает соглашение, поскольку имя метода не начинается с new или alloc и не содержит copy.Вы должны return [products autorelease]; вместо этого.Однако даже это не очень удобно, поскольку products не объявлено локально, вероятно, это переменная экземпляра.В таком случае, что произойдет, если processXML будет вызван несколько раз?У вас есть собственный объект, на который ссылается переменная экземпляра, и вдруг вы перезаписываете эту ссылку новой ... = утечка памяти.
  3. Каждый раз, когда кто-то делает MyClass * object = [[MyClass alloc] init]; object = [something thatReturnsAMyClass];, котенок умирает.Если вы затем сделаете [object release];, за хорошую меру умирает секунда.Это ужасная утечка памяти (и вероятный сбой).Вы выделяете новый объект, а затем немедленно выбрасываете его, но никогда не отпускаете.То, что вы делаете это, говорит о том, что вы на самом деле не понимаете, что такое указатель.Я предлагаю прочитать « Все, что вам нужно знать об указателях в C »
  4. На более легкой ноте вы должны проверить -[NSString stringByAppendingPathComponent:]NSString есть куча действительно хороших методов для работы с путями.

Надеюсь, я не буду слишком резким.:)

1 голос
/ 14 декабря 2010

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

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

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

Итак, чтобы действительно помочь вам прочитать эту главу: Руководство по программированию управления памятью

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...