UITableView Memory Leak

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • UITableView Memory Leak

    Moin,
    ich bin gerade beim debuggen meines Projekts und kämpfe noch mit einem Memory-Leak den ich mir nicht erklären kann.
    Ich habe eine Tabelle in der einzelnen Reihen verschiedene UITableViewCellStyles zuweise. Die Implementation sieht so aus:

    Quellcode

    1. -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    2. static NSString *cellIdentifier1 = @"cell1";
    3. static NSString *cellIdentifier2 = @"cell2";
    4. static NSString *cellIdentifier3 = @"cell3";
    5. static NSString *cellIdentifier4 = @"cell4";
    6. static NSString *cellIdentifier5 = @"cell5";
    7. static NSString *cellIdentifier6 = @"cell6";
    8. UITableViewCell *cell;
    9. if ([indexPath section] == 0.0) {
    10. if ([indexPath row] == 0.0 ) {
    11. cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier1];
    12. cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier1] autorelease];
    13. //Zelleninhalte...
    14. }else if ([indexPath row] == 1.0) {
    15. cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier2];
    16. cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier2] autorelease];
    17. //Zelleninhalte...
    18. }else if ([indexPath row] == 2.0) {
    19. cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier3];
    20. cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier3] autorelease];
    21. //Zelleninhalte...
    22. }else if([indexPath row] == 3.0){
    23. cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier4];
    24. cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier4] autorelease];
    25. //Zelleninhalte...
    26. }else if([indexPath row] == 4.0){
    27. cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier5];
    28. cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier5] autorelease];
    29. //Zelleninhalte...
    30. }else if([indexPath row] == 5.0){
    31. cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier6];
    32. cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier6] autorelease];
    33. //Zelleninhalte...
    34. }
    35. }
    36. return cell;
    37. }
    Alles anzeigen


    Beim Laden der Tabelle leckt noch nichts, erst wenn ich die Tabelle wiederholt schnell hoch und runter scrolle. Ich habe bewusst die Zelleninhalte auskommentiert, da ich dachte es ist irgendein Bild oder Ähnliches das das Speicherleck verursacht. Das lässt darauf schließen das es die einzelnen Zellen sind die das Problem verursachen. Da ich die Zellen ja aber in den Autorelease Pool packe dachte ich, dass ich auf der sicheren Seite bin.

    Kann mir jemand sagen wo mein Fehler liegt?

    Danke und Grüße, Marco


    Im Anhang noch die restlichen Methoden für die Zellen:

    Quellcode

    1. -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    2. return 1.0;
    3. }
    4. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    5. int secCount;
    6. if (section == 0.0) {
    7. secCount = 6.0;
    8. }else {
    9. secCount = 0.0;
    10. }
    11. return secCount;
    12. }
    13. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    14. if ([indexPath section] == 0.0) {
    15. if ([indexPath row] == 0.0 ){
    16. return 60.0;
    17. }else if ([indexPath row] == 1.0) {
    18. return 20.0;
    19. }else if ([indexPath row] == 2.0) {
    20. return 60.0;
    21. }else{
    22. return 50.0;
    23. }
    24. }
    25. return 40.0;
    26. }
    Alles anzeigen
  • Ist das grausig. Hast Du schon mal etwas von Wiederverwendung gehört?

    Wenn Du eine Zelle mit dequeueReusableCellWithIdentifier: holst, dann solltest Du nur dann eine neue Zelle anlegen, wenn das Ergebnis der Methode nil. Also

    Quellcode

    1. UITableViewCell *theCell = [inTableView dequeueReusableCellWithIdentifier:theIdentifier];
    2. if(theCell == nil) {
    3. theCell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:theIdentifier] autorelease];
    4. }
    5. // theCell füllen

    Einer Variablen in zwei aufeinanderfolgenden Anweisungen einen Wert zuweisen ist Mumpiz. Die erste Zuweisung kannst Du Dir sparen, weil der Wert durch die zweite futsch ist.
    „Meine Komplikation hatte eine Komplikation.“
  • Ok, das hatte ich vorher auch so gemacht. Allerdings führte das ebenfalls zu einem Leak, daher habe ich das so umgeschrieben.

    Wie würde denn eine Implementierung mit 2 verschiedenen CellStyles aussehen? Brauche ich dann gar nicht verschiedene identifier für jede zelle?
  • Du brauchst unterschiedliche Identifier für Zellen mit unterschiedlicher Darstellung. Mehrere Identifier kannst Du beispielsweise auch über ein Array verwalten. Übrigens ist die Property row von NSIndexPath eine natürliche Zahl (developer.apple.com/iphone/lib…occ/instp/NSIndexPath/row).

    Sind die 6 Zelltypen wirklich komplett unterschiedlich oder haben die vielleicht doch die eine oder andere Gemeinsamkeit?
    „Meine Komplikation hatte eine Komplikation.“
  • macmoonshine schrieb:

    Du brauchst unterschiedliche Identifier für Zellen mit unterschiedlicher Darstellung. Mehrere Identifier kannst Du beispielsweise auch über ein Array verwalten.


    Ok, dann bin ich soweit ja schonmal auf dem richtigen Weg. Ich verstehe halt die Zuweisung noch nicht so ganz. Mit UITableViewCell *theCell = [inTableView dequeueReusableCellWithIdentifier:theIdentifier]; lege ich ja schon einen identifier für die Zelle fest. Anschließend überprüfe ich in jeder row ob die Zelle existiert. Wenn sie nicht existiert erstelle ich eine und überschreibe den Identifier mit dem für die Zelle. Wenn ich das bei jeder Zelle mache, dann kann ich mir den Aufruf doch auch eigentlich komplett sparen und den identifier direkt bei der Erstellun festlegen, oder?

    Ich kann jetzt nicht weitermachen und probiere das morgen noch mal aus und melde mich dann wieder!

    macmoonshine schrieb:

    Übrigens ist die Property row von NSIndexPath eine natürliche Zahl (developer.apple.com/iphone/librar…NSIndexPath/row).

    Danke, das hatte ich aus einem Beispiel übernommen. Ändere ich in NSInteger.


    macmoonshine schrieb:

    Sind die 6 Zelltypen wirklich komplett unterschiedlich oder haben die vielleicht doch die eine oder andere Gemeinsamkeit?

    Jein, einige Zellen haben Gemeinsamkeiten; Es gibt im Prinzip 4 verschiedene Typen.
  • So, ich hab mich nochmal an die Tabelle gesetzt. So langsam verstehe ich auch das Prinzip, das war vorher echt nicht der Fall. Ich habe den Vorschalg die Identifier in ein Array zu speichern aufgegriffen und wähle den identifier anschließend anhand der row aus. Wenn eine Zelle nicht existiert erstelle ich eine und sonst verwende ich sie wieder. Die Tabelle funktioniert, allerdings weiterhin mit dem Manko, dass mir Instruments Memory Leaks ausgibt sobald ich die Tabelle öfters schnell nach oben und unten scrolle. Woran kann das liegen? Mache ich noch was falsch? Oder hat das mit den Zellen zu tun die ich in autoreleasen lasse und der Speicher kommt nicht hinterher?!

    Quellcode

    1. NSArray *cellIDArray = [NSArray arrayWithObjects:@"cell0",@"cell1",@"cell2",@"cell3",@"cell4",@"cell5",nil];
    2. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[cellIDArray objectAtIndex:[indexPath row]]];
    3. if ([indexPath section] == 0) {
    4. if ([indexPath row] == 0) {
    5. if (cell == nil) {
    6. cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:[cellIDArray objectAtIndex:[indexPath row]]] autorelease];
    7. //customize
    8. }
    9. [....]


    Ich hänge mal einen Screenshot von Instruments an. Gibt es zu Instruments was gutes Einführendes zu lesen? Evntl. im Netz?
  • macmoonshine schrieb:

    Quellcode

    1. NSString *theCellId = [NSString withFormat:@"cell%d", [indexPath row]];

    Hast Du mehrere Sections? Ja -> Dann muss das wahrscheinlich bei der Erzeugung der CellId berücksichtigt werden. Nein -> Dann kannst Du Dir die Abfrage in Zeile 3 sparen.


    Nein, ich habe (momentan) nur eine Section, aber es ist evntl. eine Zweite angedacht. Habe die Abfrage kurz auskommentiert und das Problem besteht weiterhin. Es tritt eben auf wenn ich "heftig" hin und herscrolle, und da auch erst nach einigen Sekunden. Es sind ja nur einige wenige Bytes, aber ich würde trotzdem gerne wissen woran das liegt.

    Mache ich denn grundsätzlich einen Denkfehler oder was falsch bei der Implementierung?
  • Ich meinte nicht, dass der Leak an der Abfrage liegt...

    Klick mal in Instruments auf das rechte Icon am unteren Bildschirmrand, bis die Anzeige wie im Screenshot aussieht. Dann siehst Du rechts den Stacktrace, der zum Leak geführt hat. Doppelklick zeigt Dir den Quälcode, wenn dieser denn vorliegt. Vielleicht gibt Dir das ja einen Hinweis.

    So ganz leakfrei habe ich bislang allerdings keine App bekommen.
    „Meine Komplikation hatte eine Komplikation.“
  • Danke erstmal für deine Hilfe. Insgesamt werden 1,84KB geleakt, wenn ich 5 Minuten wie ein Irrer weiterscrolle werdens mehr. Ich denke ich kann damit leben, komisch ist es trotzdem.

    In Instruments lässt sich übrigens nur ein Objekt des Stack Trace doppelklicken, die main(). Dort finden sich dann auch übrigens die 1,84KB wieder (vgl. Screenshot).

    Wenn jemanden was dazu einfällt bin ich ganz Ohr, ansonsten betrachte ich das erstmal als abgehakt! :)