10

Ich habe gerade ein sehr ekliges UIViewController Leck, debuggen, so dass der UIViewController nicht freigegeben wurde auch nach dem Aufruf dismissViewControllerAnimated.Warum gibt eine starke Referenz auf den übergeordneten UIViewController in performBatchUpdates eine Aktivität aus?

verfolgen ich das Problem mit dem folgenden Code-Block nach unten:

self.dataSource.doNotAllowUpdates = YES; 

    [self.collectionView performBatchUpdates:^{ 
     [self.collectionView reloadItemsAtIndexPaths:@[indexPath]]; 
    } completion:^(BOOL finished) { 
     self.dataSource.doNotAllowUpdates = NO; 
    }]; 

Grundsätzlich, wenn ich einen Anruf zu performBatchUpdates mache und dann sofort dismissViewControllerAnimated rufe, werden die UIViewController durchgesickert und die dealloc Methode dieser UIViewController nie wird angerufen. Der UIViewController bleibt für immer hängen.

Kann jemand dieses Verhalten erklären? Ich nehme an, performBatchUpdates läuft über ein Zeitintervall, sagen wir, 500 ms, so würde ich davon ausgehen, dass nach diesem Intervall, würde diese Methoden aufrufen und dann die Dealloc auslösen.

Das Update scheint dies zu sein:

self.dataSource.doNotAllowUpdates = YES; 

    __weak __typeof(self)weakSelf = self; 

    [self.collectionView performBatchUpdates:^{ 
     __strong __typeof(weakSelf)strongSelf = weakSelf; 

     if (strongSelf) { 
      [strongSelf.collectionView reloadItemsAtIndexPaths:@[indexPath]]; 
     } 
    } completion:^(BOOL finished) { 
     __strong __typeof(weakSelf)strongSelf = weakSelf; 

     if (strongSelf) { 
      strongSelf.dataSource.doNotAllowUpdates = NO; 
     } 
    }]; 

Beachten Sie, dass die BOOL Membervariable, doNotAllowUpdates, ist eine Variable, fügte ich hinzu, dass verhindert jede Art von Datasource/Collection Updates, während ein Anruf zu performBatchUpdates läuft.

Ich suchte online nach Diskussionen darüber, ob wir das weakSelf/strongSelf-Muster in performBatchUpdates verwenden sollten, aber haben nichts speziell zu dieser Frage gefunden.

Ich bin froh, dass ich diesem Bug auf den Grund gehen konnte, aber ich würde einen intelligenteren iOS-Entwickler lieben, der mir dieses Verhalten erklärt, das ich sehe.

+0

Es wäre interessant zu sehen, ob Sie herausfinden könnten, ob der Aktualisierungsblock oder der Completion-Block den Retain-Zyklus verursacht hat. Wie Sie sagen, sollten beide ihren Block nicht dauerhaft festhalten - ich kann nur annehmen, dass, wenn die Sammlungsansicht aus ihrem Superview entfernt wird, keine Batchaktualisierungen mehr ausgeführt werden und der Komplettierungsblock nicht aufgerufen oder freigegeben wird. Es ist meiner Meinung nach ein Radar wert. – jrturton

+0

@jrturton Ahh das scheint die wahrscheinlichste Erklärung! Ich werde sehen, ob ich dies im Debugger basierend auf dieser Einsicht wiederholen kann. – esilver

+0

@jrturton leider nicht in Debugger repro ... es scheint, als ob beide Blöcke, zumindest, immer aufgerufen werden. Vielleicht werden die Interna die Blöcke nicht nivellieren, wenn die Kündigung in der Mitte aufgerufen wird? – esilver

Antwort

-1

Wie Sie herausgefunden haben, wenn weak nicht verwendet wird, wird ein Retain-Zyklus erstellt.

Der Zyklus beibehalten wird durch self verursacht collectionView eine starke Referenz aufweist und collectionView hat jetzt einen starken Bezug auf self.

Man muss immer davon ausgehen, dass self freigegeben sein könnte, bevor ein asynchroner Block ausgeführt wird. Um damit umzugehen sicher zwei Dinge getan werden muss:

  1. immer einen schwachen Verweis auf self (oder die Ivar selbst)
  2. bestätigen immer weakSelf existiert, bevor es als nunnull param

vorbei UPDATE:

Putting ein wenig Protokollierung performBatchUpdates bestätigt eine Menge:

- (void)logPerformBatchUpdates { 
    [self.collectionView performBatchUpdates:^{ 
     NSLog(@"starting reload"); 
     [self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]]; 
     NSLog(@"finishing reload"); 
    } completion:^(BOOL finished) { 
     NSLog(@"completed"); 
    }]; 

    NSLog(@"exiting"); 
} 

Drucke:

starting reload 
finishing reload 
exiting 
completed 

Dies zeigt, dass der Abschluss Block nach dem Verlassen des aktuellen Bereichs abgefeuert wird, das heißt, sie asynchron zurück zu dem Haupt-Thread abgesendet wird.

Sie erwähnen, dass Sie den View-Controller nach dem Batch-Update sofort schließen. Ich denke, das ist die Wurzel Ihres Problems:

Nach ein paar Tests war die einzige Möglichkeit, die Speicherleck neu erstellen konnte, indem Sie die Arbeit vor der Kündigung geschickt. Es ist ein langer Schuss, aber nicht Ihr Code wie folgt aussehen zufällig ?:

- (void)breakIt { 
    // dispatch causes the view controller to get dismissed before the enclosed block is executed 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     [self.collectionView performBatchUpdates:^{ 
      [self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]]; 
     } completion:^(BOOL finished) { 
      NSLog(@"completed: %@", self); 
     }]; 
    }); 
    [self.presentationController.presentingViewController dismissViewControllerAnimated:NO completion:nil]; 
} 

Der obige Code führt zu dealloc nicht auf dem View-Controller aufgerufen wird.

Wenn Sie Ihren bestehenden Code und einfach versenden (oder performSelector: nach :) dismissViewController Anruf Sie werden wahrscheinlich das Problem auch beheben.

+0

Sie sind auf allen Fronten richtig, jedoch, wie Sie aus meinem Code-Schnipsel oben sehen können, auch mit dem starken Bezug auf Selbst, sollte es bereinigt werden, nachdem der Vervollständigungsblock ausgeführt wurde und der Retain-Zyklus freigegeben wurde. Es ist mir nicht klar, wie ein Retain-Zyklus erstellt wird und der UIViewController nie freigegeben wird, geben Sie mein spezifisches Code-Snippet oben ein. – esilver

+0

sollte der Retain-Zyklus ziemlich klar sein. self behält die Sammlungsansicht bei und die Sammlungsansicht behält sich nun selbst bei. Einer dieser Zeiger muss schwach sein, um den Retain-Zyklus zu verhindern (also "weakSelf" zu machen). Ich bin nicht sicher, welche Bereinigung Sie beziehen, da UICollectionView eine Blackbox ist ... ich würde nicht erwartet, dass collectionView den Completion-Block ausschließt und Sie sollten sich definitiv nicht darauf verlassen – Casey

+0

Lassen Sie uns Ihre Logik vervollständigen: nehmen Sie an, UICollectionView nicht nil seinen Abschlussblock. UICollectionView startet die Batch-Aktualisierung und ruft den ersten (stark beibehaltenen) Block auf. UIViewController wird beendet, kann jedoch nicht bereinigt werden, da der zweite Vervollständigungsblock eine starke Referenz hat. 100 ms gehen für Animationseffekte vorbei. UICollectionView ruft seinen Abschlussblock auf. Nun werden alle Blöcke ausgeführt, Verweise auf diese Blöcke sollten aufgeräumt/nicht gel \ u00f6st werden, und UIViewController hat keine Referenzen mehr und kann die Zuordnung aufheben.Dies sollte die Freigabe nur verzögern, nicht verhindern. Recht? – esilver

0

Dies scheint wie ein Fehler mit UICollectionView. API-Benutzer sollten nicht davon ausgehen, dass einzelne Ausführungsparameter über die Ausführung der Task hinaus beibehalten werden. Daher sollte die Vermeidung von Referenzzyklen kein Problem darstellen.

UICollectionView sollte alle Verweise auf Blöcke aufräumen, nachdem der Stapelaktualisierungsprozess beendet wurde oder wenn der Stapelaktualisierungsprozess unterbrochen wurde (z. B. indem die Sammlungsansicht vom Bildschirm entfernt wurde).

Sie haben selbst gesehen, dass der Completion-Block aufgerufen wird, auch wenn die Sammlungsansicht während des Aktualisierungsvorgangs außerhalb des Bildschirms angezeigt wird. Daher sollte die Sammlungsansicht alle Referenzen auf diesen Komplettierungsblock anzeigen wird nie wieder aufgerufen, unabhängig vom aktuellen Status der Sammlungsansicht.