2010-12-13 21 views
0

Ich habe eine Produkt-Display-App gearbeitet, aber es hat einen Speicherverlust, der es zum Absturz bringt, nachdem zu viele Kategorien geladen wurden. Die App funktioniert über einen SplitViewController, der die Kategorien auf der linken Seite auflistet und die angeklickten Produktbilder im DetailViewController auf der rechten Seite anzeigt. Wenn Sie die Kategorie nach der Kategorie auswählen, stürzt die App schließlich ab.Speicherverlust in iPad app

Ich habe die Instrumente -> Leaks-Tool verwendet, um das Problem zu verfolgen, und mir wird gesagt, dass NSString appendString ein Leck ist. Die Anzahl der durchgesickerten Strings scheint mit der Anzahl der Produkte in der ausgewählten Kategorie übereinzustimmen, also vermute ich, dass einer meiner Loops das Problem behebt, aber nachdem ich mit AutoreleasePools herumgespielt habe, habe ich es noch nicht gelöst.

Mein Code: Diese Methode wird aufgerufen, wenn die Kategorie ausgewählt und analysiert ein XML-Dokument

- (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; 

}

Wie Sie mich mit autoReealse auf meinen Saiten ging ein wenig verrückt zu sehen. Das andere Code-Segment, das die Bilder anzeigt, könnte das Problem sein, obwohl Leaks ProcessXML direkt erwähnt.

- (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 ist ein autoreleasePool in der h-Datei für die Klasse definiert.

Ich würde wirklich jede spezifische Hilfe in Bezug auf meinen Code oder allgemeine Tipps, was falsch sein könnte, schätzen.

+4

'. [[NSString string: @ ""] Autorelease];' *** DAS NICHT TUN *** Sie overreleasing diese Objekte und Sie werden * dein Programm zum Absturz bringen *. –

+0

Ihr Code enthält keine Aufrufe von 'appendString:', von denen Sie behaupten, dass sie als Quelle des Lecks identifiziert wurden. Dies führt auch nicht zu einem Leck, aber all diese "[[NSString stringWithFormat: whatever] Autorelease]" - Zeilen sind absolut falsch und werden sehr wahrscheinlich einen Absturz verursachen. Sie besitzen die Zeichenfolge nicht, also dürfen Sie sie nicht freigeben. http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html – Chuck

Antwort

2

Ich sehe ein paar Dinge falsch:

  1. Wie in den Kommentaren erwähnt wurde, sind Sie -autorelease in einer Weise zu missbrauchen, die erwachsene Männer weinen und das, was Ihre Anwendung abstürzt.
  2. -processXML gibt ein Objekt im Besitz zurück. Sie vergeben products und geben es zurück. Dies bricht die Konvention, weil der Methodenname nicht mit new oder alloc beginnt und copy nicht enthält. Sie sollten stattdessen return [products autorelease];. Aber auch das ist zwielichtig, denn da products nicht lokal deklariert ist, handelt es sich wahrscheinlich um eine Instanzvariable. Was passiert in diesem Fall, wenn processXML mehrmals aufgerufen wird? Sie haben ein Objekt im Besitz, auf das von der Instanzvariable verwiesen wird, und plötzlich überschreiben Sie diese Referenz mit einer neuen ... = Speicherleck.
  3. Jedes Mal, wenn jemand MyClass * object = [[MyClass alloc] init]; object = [something thatReturnsAMyClass]; tut, stirbt ein Kätzchen. Wenn Sie dann [object release]; tun, stirbt eine zweite für gutes Maß. Dies ist ein schreckliches, schreckliches Speicherleck (und ein wahrscheinlicher Absturz). Sie ordnen ein neues Objekt zu und werfen es sofort weg, geben es aber nie wieder frei. Dass Sie das tun, deutet darauf hin, dass Sie nicht wirklich verstehen, was ein Zeiger ist. Ich schlage vor, "Everything you need to know about pointers in C" zu lesen
  4. Auf eine leichtere Anmerkung sollten Sie heraus überprüfen -[NSString stringByAppendingPathComponent:]. NSString hat eine Reihe von wirklich schönen Methoden für den Umgang mit Pfaden.

Ich hoffe, ich komme nicht zu hart. :)

+0

Nie zu hart, Dave. Ich wusste, dass ich Autorelease missbrauchte und es ging gegen das, was ich in der Dokumentation gelesen habe, aber ich konnte das Leck von appendString nicht aufspüren. Ich wurde verzweifelt. Ich habe das Produkt geändert, um diese Seite zu reparieren. Suche immer noch nach diesen appendString. – Steve

1

Vor einiger Zeit an einem anderen Post sagte jemand, dass man über die Speicherverwaltung lesen sollte und ich dachte eigentlich, dass diese Antwort nicht richtig ist. Was ist los mit etwas Versuch und Irrtum und Lernen durch Tun.Aber nachdem ich meine schmerzhaften Erfahrungen mit dem Gedächtnis gemacht habe, muss ich zugeben, dass dieser Typ recht hatte. Nimm die Zeit. Lesen Sie das Kapitel zur Speicherverwaltung in der Apple-Dokumentation.

Wie oben bereits erwähnt, sollten Sie ein Objekt, das Ihnen nicht gehört, nicht automatisch freigeben. Aber das könnte nicht den Ärger verursachen. Sie können neben den Instrumenten Build + Analyse im Menü Erstellen verwenden. Dies wird Ihnen helfen, mehr zu erfahren.

Im Grunde müssen Sie Objekte freigeben, die Sie erstellen, die Sie besitzen (die, die Sie besitzen, ist in der Dokumentation, im Grunde die mit "Alloc" erstellt und einige mehr). Wenn Sie sie nicht freigeben können, weisen Sie sie dem Autorelease-Pool zu. Dies ist der Fall bei den "Produkten", die Sie von processXML zurückgeben. Wann wird der Autorelease-Pool geleert? Dies ist, wenn das Framework der Anwendung das nächste Mal wieder unter Kontrolle ist (ich glaube, es hieß Run-Loop oder etwas). Dies kann eine Weile dauern und Sie sollten nicht zu viele Objekte öffnen, die einem Autorelease-Pool zugewiesen sind.

So Sie wirklich helfen, dieses Kapitel zu lesen: memory management programming guide