2010-12-13 68 views
0

Je travaille sur une application d'affichage de produit, mais il y a une fuite de mémoire qui provoque son arrêt brutal après que trop de catégories ont été chargées. L'application fonctionne via un SplitViewController qui liste les catégories à gauche et, une fois tapées, les images du produit s'affichent dans le detailViewController sur la droite. La sélection de la catégorie après la catégorie finit par bloquer l'application.fuite de mémoire dans l'application iPad

J'ai utilisé l'outil instruments -> Leaks pour tracer le problème et on me dit que NSString appendString est une fuite. Le nombre de chaînes fuites semble correspondre au nombre de produits dans la catégorie sélectionnée, donc je suppose que l'une de mes boucles contient le problème mais, après avoir joué avec AutoreleasePools, je ne l'ai pas encore résolu.

Mon code: Cette méthode est appelée lorsque la catégorie est sélectionnée et parse un 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; 

}

Comme vous pouvez le voir, je suis un peu fou avec autoReealse sur mes cordes. L'autre segment de code qui affiche les images pourrait être le problème, bien que Leaks mentionne directement 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]; 

}

Par ailleurs, la piscine est un autoreleasePool défini dans le fichier h pour la classe.

J'apprécierais vraiment toute aide spécifique concernant mon code ou des conseils généraux sur ce qui pourrait être faux.

+4

'. [[NSString stringWithFormat: @ ""] autorelease];' *** NE PAS FAIRE CE *** Vous overreleasing ces objets et ils vont * planter votre programme *. –

+0

Votre code n'inclut aucun appel à 'appendString:', qui selon vous, est identifié comme la source de la fuite. De plus, cela ne causera pas de fuite, mais toutes ces lignes [[NSString stringWithFormat: whatever] autorelease] 'sont absolument fausses et provoqueront probablement un crash. Vous n'êtes pas propriétaire de la chaîne, vous ne devez donc pas la libérer. http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html – Chuck

Répondre

2

Je vois quelques mauvaises choses:

  1. Comme cela a été mentionné dans les commentaires, vous abusez -autorelease des moyens qui font pleurer les hommes cultivés et ce qui se bloque votre application.
  2. -processXML renvoie un objet appartenant. Vous allouez products et le renvoyez. Cela casse la convention, car le nom de la méthode ne commence pas par new ou alloc et ne contient pas copy. Vous devriez plutôt return [products autorelease];. Cependant, même cela est louche, car puisque products n'est pas déclaré localement, il s'agit probablement d'une variable d'instance. Dans ce cas, que se passe-t-il si processXML est invoqué plusieurs fois? Vous avez un objet possédé référencé par la variable d'instance, et soudainement vous remplacez cette référence par une nouvelle ... = fuite de mémoire.
  3. Chaque fois que quelqu'un fait MyClass * object = [[MyClass alloc] init]; object = [something thatReturnsAMyClass];, un chaton meurt. Si vous faites ensuite [object release];, une seconde meurt pour faire bonne mesure. C'est une terrible, terrible fuite de mémoire (et un crash probable). Vous allouez un nouvel objet, puis vous le jetez immédiatement sans le relâcher. Que vous faites cela suggère que vous n'obtenez pas vraiment ce qu'est un pointeur. Je suggère de lire "Everything you need to know about pointers in C"
  4. Sur une note plus légère, vous devriez vérifier -[NSString stringByAppendingPathComponent:]. NSString a un tas de méthodes vraiment sympa pour gérer les chemins.

J'espère ne pas être trop dur. :)

+0

Jamais trop dur, Dave. Je savais que j'utilisais l'autorelease de manière abusive et que cela allait à l'encontre de ce que j'avais lu dans la documentation mais que j'étais incapable de localiser la fuite d'appendString que je devenais désespérée. J'ai modifié l'objet products pour corriger ce côté. Toujours à la chasse de ces appendString. – Steve

1

Il y a quelque temps, à un autre poste, quelqu'un a dit qu'il fallait lire sur la gestion de la mémoire et je pensais en fait que cette réponse n'était pas vraiment juste. Quel est le problème avec certains essais et erreurs et apprendre en faisant.Mais après avoir eu mes expériences douloureuses avec la mémoire, je dois admettre que ce type avait raison. Prendre le temps. Allez lire le chapitre sur la gestion de la mémoire dans la documentation Apple.

Comme indiqué précédemment, vous ne devriez pas autoriser un objet que vous ne possédez pas. Mais cela pourrait ne pas causer le problème. Vous pouvez à côté des instruments utiliser Build + Analyze dans le menu Build. Cela vous aidera à en savoir plus.

Fondamentalement, vous devez libérer les objets que vous créez (ceux que vous possédez sont dans la documentation, essentiellement ceux créés avec "alloc" et quelques autres). Si vous ne pouvez pas les libérer, vous les attribuez au pool autorelease. C'est le cas des "produits" que vous renvoyez de processXML. Quand le pool d'autorelease est-il drainé? C'est quand la prochaine fois que le cadre de l'application est de retour en contrôle (je pense qu'il a été appelé run-loop ou quelque chose). Cela peut prendre du temps et vous ne devriez donc pas ouvrir beaucoup d'objets qui sont assignés à un pool autorelease.

Donc, pour vous aider à lire vraiment ce chapitre: memory management programming guide