Das Schreiben von Core Data-Code mit Blick auf die Leistung hilft, Ihre App für die Zukunft vorzubereiten. Ihre Datenbank mag am Anfang klein sein, kann aber leicht wachsen, was zu langsamen Abfragen und weniger Erfahrung für den Benutzer führt.
Seit ich 2017 mit dem Schreiben der Collect by WeTransfer-App begonnen habe, schreibe ich eine Menge Code für Core Data und berühre ihn fast jeden Tag. Mit Millionen von Benutzern, die viele Inhalte hinzufügen, ist das Ausführen von Code im Zusammenhang mit Kerndaten zu einer wichtigen Fähigkeit in unserem Team geworden.
Im Laufe der Jahre haben wir viele Erkenntnisse entwickelt, die ich gerne mit Ihnen durch 5 Tipps teile, die Sie kennen sollten.
- 1: Verwenden Sie einen Hintergrundkontext für verwaltete Objekte
- Wichtig: Übergeben Sie keine NSManagedObject-Instanzen zwischen Warteschlangen
- 2: Speichern Sie einen verwalteten Objektkontext nur bei Bedarf
- Überlegen Sie sorgfältig, wann Sie Ihre Änderungen speichern sollen
- 3: Nur holen, was Sie brauchen
- 4: Verwenden Sie Abruflimits
- 5: Löschen Sie viele Objekte gleichzeitig mit einem NSBatchDeleteRequest
- 6: Wissen, wie man Core Data Code debuggt
- Fazit
1: Verwenden Sie einen Hintergrundkontext für verwaltete Objekte
Eine Sache, die wir von Anfang an nicht getan haben, ist die Verwendung eines Hintergrundkontexts für verwaltete Objekte. Wir haben den Ansichtskontext nur verwendet, um kerndatenbezogene Aufgaben auszuführen: Einfügen neuer Inhalte, Löschen von Inhalten, Abrufen von Inhalten usw.
Am Anfang war unsere App relativ klein. Die ausschließliche Verwendung des Ansichtskontexts war kein wirkliches Problem und führte zu keinen sichtbaren Leistungseinbußen im Zusammenhang mit Kerndaten. Sobald unsere App zu wachsen begann, stellten wir natürlich fest, dass der Ansichtskontext mit der Hauptwarteschlange verknüpft war. Langsame Abfragen blockierten unsere Benutzeroberfläche und unsere App reagierte weniger.
Im Allgemeinen empfiehlt es sich, die Datenverarbeitung in einer Hintergrundwarteschlange durchzuführen, da dies CPU-intensiv sein kann. Beispiele wie das Importieren von JSON in Core Data könnten andernfalls den Ansichtskontext blockieren und zu einer nicht reagierenden Benutzeroberfläche führen.
Die Lösung besteht darin, einen hintergrundverwalteten Objektkontext zu verwenden. Mit den neuesten APIs können Sie ganz einfach einen neuen Kontext aus Ihrem persistenten Container erstellen:
let backgroundContext = persistentContainer.newBackgroundContext()
Ich empfehle diese Methode gegenüber dem NSManagedObjectContext(concurrenyType:)
Initialisierer, da sie automatisch mit dem NSPersistentStoreCoordinator
verknüpft wird und auch NSManagedObjectContextDidSave
Broadcasts verbraucht. Dadurch wird Ihr Hintergrundkontext mit dem Ansichtskontext synchronisiert.
Sie können diesen Hintergrundkontext in einer benutzerdefinierten persistenten Container-Unterklasse speichern. Auf diese Weise können Sie Ihren Hintergrundkontext wiederverwenden und müssen nur zwei Kontexte verwalten. Dies hält Ihre Kerndatenstruktur einfach zu verstehen und verhindert, dass mehrere nicht synchrone Kontexte vorhanden sind.
Wenn Sie den Hintergrundkontext nur an wenigen Stellen verwenden müssen, können Sie auch die Methode performBackgroundTask(_:)
verwenden, die einen Hintergrundkontext erstellt:
persistentContainer.performBackgroundTask { (backgroundContext) in // .. Core Data Code}
Diese Methode erstellt jedoch bei jedem Aufruf eine neue NSManagedObjectContext
. Möglicherweise möchten Sie den freigegebenen Hintergrundkontext verwenden, wenn Sie häufiger an einen Hintergrundkontext senden.
Wichtig: Übergeben Sie keine NSManagedObject-Instanzen zwischen Warteschlangen
Das Schreiben von Multithread-Core-Data-Code ist viel komplexer als die Verwendung eines einzelnen Ansichtskontexts. Der Grund dafür ist, dass Sie ein NSManagedObject
instanziiertes NSManagedObject
aus einem Ansichtskontext nicht einfach an einen Hintergrundkontext übergeben können. Dies würde zu einem Absturz und einer möglichen Datenbeschädigung führen.
Wenn ein verwaltetes Objekt von einer Warteschlange in eine andere verschoben werden muss, können Sie NSManagedObjectID
verwenden, das threadsicher ist:
let managedObject = NSManagedObject(context: persistentContainer.viewContext)backgroundContext.perform { let object = try? backgroundContext.existingObject(with: managedObject.objectID)}
2: Speichern Sie einen verwalteten Objektkontext nur bei Bedarf
Beim Speichern eines verwalteten Objektkontexts werden alle aktuellen Änderungen in den übergeordneten Speicher des Kontexts übernommen. Wie Sie sich vorstellen können, ist dies keine billige Operation und sollte nur bei Bedarf verwendet werden, um die Leistung in Core Data sicherzustellen.
Zuallererst ist es wichtig zu überprüfen, ob es überhaupt etwas zu speichern gibt. Wenn keine Änderungen festgeschrieben werden müssen, gibt es auch keinen Grund, ein Speichern durchzuführen. Indem Sie eine saveIfNeeded
-Methode erstellen, können Sie einfach eine Überprüfung dafür einbauen:
Überlegen Sie sorgfältig, wann Sie Ihre Änderungen speichern sollen
Abgesehen von der Verwendung von saveIfNeeded
anstelle von save()
müssen Sie auch überlegen, ob ein Speichern sinnvoll ist. Obwohl ein Kontext Änderungen enthalten kann, ist es nicht immer erforderlich, diese Änderungen direkt zu übernehmen.
Wenn Sie beispielsweise mehrere Elemente in Ihre Datenbank importieren, möchten Sie sie möglicherweise erst speichern, nachdem Sie alle Elemente in Ihren Hintergrundkontext importiert haben. Auf ein Speichern folgen häufig UI-Updates, und mehrere Speicherungen nacheinander können leicht zu unnötigen Neuladungen führen. Berücksichtigen Sie außerdem, dass gespeicherte Änderungen in einem Hintergrundkontext mit dem Ansichtskontext zusammengeführt werden, wodurch auch die Hauptwarteschlange kurzzeitig blockiert wird. Deshalb sei bewusst!
3: Nur holen, was Sie brauchen
Das Abrufen von Daten ist eine teure Aufgabe und muss so performant wie möglich sein, um Ihre App für große Datensätze vorzubereiten. Der folgende Code ist ein häufig gemachter Fehler:
Dieser Code lädt alle eingefügten Objekte in den Speicher, während er direkt danach gefiltert wird, um nur mit Inhalten mit einem Namen zu verbleiben.
Es ist viel performanter, Prädikate zu verwenden, um nur die Objekte abzurufen, die benötigt werden. Der obige Filter kann wie folgt geschrieben werden: NSPredicate
:
Dies hat zwei Vorteile:
- Nur die benötigten Objekte werden in den Speicher geladen
- Sie müssen nicht über alle Objekte iterieren
Prädikate sind sehr flexibel und sollten es Ihnen ermöglichen, den gewünschten Datensatz in den meisten Fällen abzurufen, während die Leistung in Core Data erhalten bleibt.
4: Verwenden Sie Abruflimits
Im Anschluss an das vorherige Beispiel ist es wichtig, Abruflimits festzulegen, wenn Sie nur einen Teil des Datensatzes anzeigen möchten.
Angenommen, Sie benötigen nur die ersten 3 Namen aller Inhaltselemente. In diesem Fall wäre es nicht erforderlich, alle Inhaltselemente mit einem Namen in den Speicher zu laden. Wir könnten dies verhindern, indem wir ein Abruflimit festlegen:
Dieser Code gibt nur die ersten 3 Inhaltselemente mit einem Namen zurück.
5: Löschen Sie viele Objekte gleichzeitig mit einem NSBatchDeleteRequest
Anstatt über ein Dataset zu iterieren und jedes Objekt einzeln zu löschen, ist es oft performanter, ein NSBatchDeleteRequest
zu verwenden, das schneller ausgeführt wird, da es auf SQL-Ebene im persistenten Speicher selbst ausgeführt wird.
Weitere Informationen zum Löschen von Stapelanforderungen finden Sie in meinem Blogbeitrag Mit NSBatchDeleteRequest zum Löschen von Stapeln in Core Data.
6: Wissen, wie man Core Data Code debuggt
Wie bei jedem Code, den Sie schreiben, ist es wichtig zu wissen, wie man ihn optimiert und debuggt, wenn er nicht wie erwartet funktioniert. Es gibt viele Möglichkeiten zum Debuggen, die am besten in meinem speziellen Blogbeitrag erläutert werden: Core Data Debugging in Xcode using launch arguments .
Fazit
Wenn Sie von Anfang an performanten Core Data-Code schreiben, können Sie Ihre App für zukünftige große Datensätze vorbereiten. Obwohl Ihre App zu Beginn möglicherweise eine gute Leistung erbringt, kann sie sich leicht verlangsamen, sobald Ihre Datenbank und Ihr Modell wachsen. Durch die Verwendung eines Hintergrundkontexts, intelligenter Abrufanforderungen und Stapellöschanforderungen wird Ihr Kerndatencode bereits leistungsfähiger.
Wenn Sie Ihr Swift-Wissen noch weiter verbessern möchten, besuchen Sie die Swift-Kategorieseite. Fühlen Sie sich frei, mich zu kontaktieren oder mir auf Twitter zu twittern, wenn Sie zusätzliche Tipps oder Feedback haben.
Danke!