NSRangeException-Ausnahme im NSFetchedResultsChangeUpdate-Ereignis von NSFetchedResultsController

8

Ich habe eine UITableView , die eine NSFetchedResultsController als Datenquelle verwendet.

Der Hauptdatenspeicher wird in mehreren Hintergrundthreads aktualisiert, die parallel ausgeführt werden (jeder Thread verwendet sein eigenes NSManagedObjectContext ).

Der Haupt-Thread beobachtet die NSManagedObjectContextDidSaveNotification Benachrichtigung und aktualisiert seinen Kontext mit mergeChangesFromContextDidSaveNotification: .

Manchmal passiert es, dass NSFetchedResultsController ein sendet NSFetchedResultsChangeUpdate event mit einem IndexPfad, der nicht existiert mehr an diesem Punkt.

Zum Beispiel: Die Ergebnismenge des abgerufenen Ergebnis-Controllers enthält 1 Abschnitt mit 4 Objekten. Das erste Objekt wird in einem Thread gelöscht. Das letzte Objekt wird in einem anderen Thread aktualisiert. Dann manchmal die Folgendes passiert:

  • controllerWillChangeContent: wird aufgerufen.
  • controller: didChangeObject: atIndexPath: forChangeType: newIndexPath: wird mit aufgerufen type = NSFetchedResultsChangeDelete, indexPath.row = 0.
  • controller: didChangeObject: atIndexPath: forChangeType: newIndexPath: wird mit aufgerufen type = NSFetchedResultsChangeUpdate, indexPath.row = 3.

Aber der abgerufene Ergebnis-Controller enthält jetzt nur noch 3 Objekte und wenn

aufgerufen wird %Vor%

zum Aktualisieren der Tabellenansichtszelle gemäß NSFetchedResultsChangeUpdate Ereignis stürzt dies mit einer NSRangeException Ausnahme ab.

Danke für jede Hilfe oder Ideen!

    
Martin R 11.07.2012, 12:21
quelle

2 Antworten

13

Ich habe jetzt eine Lösung für mein Problem gefunden. Im Falle eines Update-Ereignisses muss kein

aufgerufen werden %Vor%

, weil das aktualisierte Objekt bereits als anObject -Parameter für den -controller:didChangedObject:... -Delegaten angegeben wurde.

Ich habe daher -configureCell:atIndexPath: durch eine -configureCell:withObject: -Methode ersetzt, die das aktualisierte Objekt direkt verwendet. Dies scheint ohne Probleme zu funktionieren.

Der Code sieht jetzt so aus:

%Vor%     
Martin R 16.07.2012, 11:28
quelle
5

Dies ist eigentlich ziemlich häufig wegen des Fehlers in Apples Kesselplattencode für NSFetchedResultsControllerDelegate , die Sie erhalten, wenn Sie ein neues Master- / Detailprojekt mit aktivierten Core Data erstellen:

%Vor%

Lösung # 1: Verwende anObject

Warum sollten Sie den abgerufenen Ergebniscontroller abfragen und riskieren, einen falschen Indexpfad zu verwenden, wenn Ihnen das Objekt bereits übergeben wurde? Martin R empfiehlt diese Lösung auch .

Ändern Sie einfach die Hilfsmethode configureCell:atIndexPath: , indem Sie einen Indexpfad verwenden, um das tatsächlich geänderte Objekt zu übernehmen:

%Vor%

Verwenden Sie in Zelle für Zeile:

%Vor%

Schließlich, in der Aktualisierung verwenden:

%Vor%

Lösung # 2: Verwende newIndexPath

Ab iOS 7.1 werden sowohl indexPath als auch newIndexPath übergeben, wenn ein NSFetchedResultsChangeUpdate auftritt.

Halten Sie einfach die Standardimplementierung von indexPath beim Aufruf von cellForRowAtIndexPath, aber ändern Sie den zweiten Indexpfad, der an newIndexPath gesendet wird:

%Vor%

Lösung # 3: Lädt Zeilen im Indexpfad

neu

Ole Begemanns Lösung besteht darin, die Indexpfade neu zu laden. Ersetzen Sie den Aufruf zum Konfigurieren der Zelle durch einen Aufruf zum erneuten Laden von Zeilen:

%Vor%

Bei dieser Methode gibt es zwei Nachteile:

  1. Durch den Aufruf von reload rows ruft es cellForRow auf, welches wiederum dequeueReusableCellWithIdentifier aufruft, welches eine existierende Zelle wiederverwenden wird, möglicherweise wichtigen Zustand loswerden (zB wenn die Zelle mitten im Sein ist) hat einen la Mailbox-Stil gezogen.
  2. Es wird fälschlicherweise versuchen und eine Zelle neu laden, die nicht sichtbar ist . In Apples ursprünglichem Code cellForRowAtIndexPath: gibt " nil " zurück, wenn die Zelle nicht sichtbar ist oder indexPath außerhalb des Bereichs liegt. " Daher wäre es korrekter, dies mit zu überprüfen indexPathsForVisibleRows vor dem Aufruf von Reload-Zeilen.

Reproduzieren des Fehlers

  1. Erstellen Sie ein neues Master- / Detailprojekt mit Core-Daten in Xcode 6.4.
  2. Fügen Sie dem Core-Datenobjekt event ein Titelattribut hinzu.
  3. Füllen Sie die Tabelle mit mehreren Datensätzen auf (z. B. in viewDidLoad führen Sie diesen Code aus)

    %Vor%
  4. Ändern Sie die Konfigurationszelle, um das Titelattribut anzuzeigen:

    %Vor%
  5. Zusätzlich zum Hinzufügen eines Datensatzes, wenn die neue Schaltfläche angetippt wird, aktualisieren Sie das letzte Element (Hinweis: Dies kann vor oder nach dem Erstellen des Elements erfolgen, aber stellen Sie sicher, dass Sie es vor dem Speichern ausführen!):

    %Vor%
  6. Führen Sie die App aus. Sie sollten fünf Elemente sehen.

  7. Tippen Sie auf die neue Schaltfläche. Sie werden sehen, dass oben ein neues Element hinzugefügt wird und dass das letzte Element nicht den Text "aktualisiert" hat, obwohl es es hätte haben sollen. Wenn Sie das Laden der Zelle erzwingen (z. B. durch Scrollen der Zelle vom Bildschirm), wird der Text "aktualisiert" angezeigt.
  8. Implementieren Sie nun eine der drei oben beschriebenen Lösungen und zusätzlich zu einem hinzuzufügenden Element wird der Text des letzten Elements in "aktualisiert" geändert.
Senseful 25.08.2015 20:04
quelle