Vor kurzem habe ich begonnen, an einem großen Projekt mit Core Data zu arbeiten. Das Übliche ist, dass sich Menschen in Projekten ändern, Erfahrung verloren geht und die Nuancen vergessen werden. Es ist unmöglich, jeden in das Studium eines bestimmten Rahmens zu vertiefen - jeder hat seine eigenen Arbeitsprobleme. Aus diesem Grund habe ich eine kurze Präsentation mit den Punkten vorbereitet, die ich für wichtig oder unzureichend in den Tutorials halte. Ich teile mit allen und hoffe, dass dies hilft, effektiven Code zu schreiben und keine Fehler zu machen. Es wird davon ausgegangen, dass Sie sich bereits ein wenig mit dem Thema befassen.
Ich werde mit dem Banalen beginnen.
Core Data ist ein Framework, das Daten in einer Anwendung verwaltet und speichert. Sie können Core Data als Wrapper für einen physischen relationalen Speicher betrachten, der Daten in Form von Objekten darstellt, während Core Data selbst keine Datenbank ist.
Kerndatenobjekte

Zum Erstellen von Speicher verwendet die Anwendung die
Klassen NSPersistentStoreCoordinator oder
NSPersistentContainer . NSPersistentStoreCoordinator erstellt basierend auf dem Modell einen Speicher des angegebenen Typs. Sie können den Speicherort und zusätzliche Optionen angeben. NSPersistentContainer kann mit iOS10 verwendet werden und bietet die Möglichkeit, mit einer minimalen Menge an Code zu erstellen.
Es funktioniert wie folgt: Wenn eine Datenbank auf dem angegebenen Pfad vorhanden ist, überprüft der Koordinator ihre Version und führt gegebenenfalls eine Migration durch. Wenn die Datenbank nicht vorhanden ist, wird sie basierend auf dem NSManagedObjectModel-Modell erstellt. Damit dies alles ordnungsgemäß funktioniert, erstellen Sie vor dem Vornehmen von Änderungen am Modell eine neue Version in Xcode über das Menü Editor -> Modellversion hinzufügen. Wenn Sie den Pfad erhalten, können Sie die Basis im Emulator finden und öffnen.
Beispiel mit NSPersistentStoreCoordinatorvar persistentCoordinator: NSPersistentStoreCoordinator = { let modelURL = Bundle.main.url(forResource: "Test", withExtension: "momd") let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL!) let persistentCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel!) let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] let storeURL = URL(fileURLWithPath: documentsPath.appending("/Test.sqlite")) print("storeUrl = \(storeURL)") do { try persistentCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: [NSSQLitePragmasOption: ["journal_mode":"MEMORY"]]) return persistentCoordinator } catch { abort() } } ()
Beispiel für NSPersistentContainer var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "CoreDataTest") container.loadPersistentStores(completionHandler: { (storeDescription, error) in print("storeDescription = \(storeDescription)") if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) return container } ()
Core Data verwendet vier Speichertypen:
- SQLite
- binär
- In-Memory
- XML (nur Mac OS)
Wenn Sie beispielsweise aus Sicherheitsgründen keine Daten in einer Dateiform speichern möchten, aber gleichzeitig das Caching während der Sitzung und Daten in Form von Objekten verwenden möchten, ist der Speicher vom Typ "In-Memory" durchaus geeignet. Tatsächlich ist es nicht verboten, mehrere verschiedene Arten von Speichern in einer Anwendung zu haben.
Ich möchte ein paar Worte zum
NSManagedObjectContext- Objekt sagen. Im Allgemeinen bietet Apple einen sehr vagen Wortlaut für NSManagedObjectContext - eine Umgebung für die Arbeit mit Core Data-Objekten. All dies aus dem Wunsch heraus, sich von Assoziationen mit relationalen Datenbanken zu distanzieren und Core Data als benutzerfreundliches Tool zu präsentieren, das kein Verständnis für Schlüssel, Transaktionen und andere Bazdan-Attribute erfordert. In der Sprache relationaler Datenbanken kann NSManagedObjectContext jedoch gewissermaßen als Transaktionsmanager bezeichnet werden. Sie haben wahrscheinlich bemerkt, dass es Speicher- und Rollback-Methoden gibt, obwohl Sie höchstwahrscheinlich nur die erste verwenden.
Ein Missverständnis dieser einfachen Tatsache führt zur Verwendung eines Einzelkontextmodells, selbst in Situationen, in denen dies nicht ausreicht. Sie bearbeiten beispielsweise ein großes Dokument und mussten gleichzeitig einige Verzeichnisse herunterladen. Wann rufen Sie save an? Wenn wir mit einer relationalen Datenbank arbeiten würden, wäre dies keine Frage, da jede Operation in einer eigenen Transaktion ausgeführt würde. Core Data bietet auch eine sehr bequeme Möglichkeit, dieses Problem zu lösen - dies ist ein Zweig des untergeordneten Kontexts. Aber leider wird dies aus irgendeinem Grund selten verwendet. Hier gibt es einen guten
Artikel zu diesem Thema.
Vererbung
Aus irgendeinem Grund, den ich nicht verstehe, gibt es eine sehr große Anzahl von Handbüchern und Beispielen, in denen die Vererbung für Entity / NSManagedObject (Tabellen) in keiner Weise verwendet wird. Inzwischen ist es ein sehr praktisches Werkzeug. Wenn Sie keine Vererbung verwenden, können Sie Attributen (Feldern) nur Werte über den KVC-Mechanismus zuweisen, der die Namen und Attributtypen nicht überprüft. Dies kann leicht zu Laufzeitfehlern führen.
Die Neudefinition der Klasse für NSManagedObject erfolgt im Core Data Designer:

Vererbung und Codegenerierung
Nachdem Sie den Klassennamen für Entity angegeben haben, können Sie die Codegenerierung verwenden und eine Klasse mit vorgefertigtem Code abrufen:


Wenn Sie den automatisch generierten Code anzeigen möchten, dem Projekt jedoch keine Dateien hinzufügen möchten, können Sie einen anderen Weg wählen: Legen Sie die Option „Codegen“ für Entität fest. In diesem Fall muss der Code in ../ DerivedData / ... gesucht werden.

Verwenden Sie die Codegenerierung, um Klassen zu erstellen. Tippfehler in Variablennamen können zu Laufzeitfehlern führen.
Hier ist ein Code wie dieser:
@objc public class Company: NSManagedObject { @NSManaged public var inn: String? @NSManaged public var name: String? @NSManaged public var uid: String? @NSManaged public var employee: NSSet? }
In Kürze hat @NSManaged die gleiche Bedeutung wie
dynamisch in Ziel C.
Core Data selbst kümmert sich um den Empfang von Daten (mit internen Accessoren) für die Attribute seiner Klassen. Wenn Sie Transitfelder haben, müssen Sie Funktionen hinzufügen, um diese zu berechnen.
Von NSManagedObject (Tabellen) geerbte Klassen hatten im Gegensatz zu anderen Klassen vor IOS10 keinen „regulären“ Konstruktor. Um ein Objekt vom Typ Company zu erstellen, musste mit NSEntityDescription ein ziemlich ungeschicktes Konstrukt geschrieben werden. Jetzt gibt es eine bequemere Methode zur Initialisierung über den Kontext (NSManagedObjectContext). Der Code ist unten. Beachten Sie den Vorteil der Vererbung beim Zuweisen von Attributen gegenüber dem KVC-Mechanismus:
Namespace für NSManagedObject
Eine weitere erwähnenswerte Sache ist der Namespace.

Sie werden keine Schwierigkeiten haben, wenn Sie an ObjectiveC oder Swift arbeiten. Normalerweise wird dieses Feld standardmäßig korrekt ausgefüllt. Bei gemischten Projekten kann es Sie jedoch überraschen, dass Sie für Klassen in Swift und ObjectiveC verschiedene Optionen festlegen müssen. In Swift muss das „Modul“ gefüllt sein. Wenn dieses Feld nicht ausgefüllt wird, wird dem Klassennamen ein Präfix mit dem Projektnamen hinzugefügt, was zu einem Laufzeitfehler führt. Lassen Sie in Objetive C das „Modul“ leer, da sonst NSManagedObject nicht gefunden wird, wenn Sie über den Klassennamen darauf zugreifen.
Verknüpfungen zwischen Objekten
Im Prinzip wird das Thema Beziehungen gut behandelt, aber ich möchte mich darauf konzentrieren, wie dem übergeordneten Element untergeordnete Entitäten hinzugefügt werden. Daher zunächst eine kurze Erinnerung an den Mechanismus zum Erstellen von Links. Betrachten Sie ein traditionelles Beispiel: Das Unternehmen besteht aus Mitarbeitern, die Verbindung ist eins zu viele:
- Stellen Sie auf jeder Seite eine Verbindung her (Tabelle)
- Danach wird das Feld Inverse verfügbar, es muss in jeder Tabelle ausgefüllt werden.


Apple besteht darauf, inverse Beziehungen anzugeben. Gleichzeitig stärkt die Inversion nicht die Kohärenz, sondern hilft Core Data dabei, Änderungen auf beiden Seiten der Verbindung zu verfolgen. Dies ist wichtig für das Zwischenspeichern und Aktualisieren von Informationen.
Es ist auch wichtig, die Löschregel korrekt anzugeben. Eine Löschregel ist eine Aktion, die mit diesem Objekt ausgeführt wird, wenn das übergeordnete Objekt gelöscht wird.
- Kaskade - Löschen aller untergeordneten Objekte, wenn das übergeordnete Objekt gelöscht wird.
- Verweigern - Verbot, einen Elternteil zu löschen, wenn ein Kind vorhanden ist
- Aufheben - Übergeordnete Referenz aufheben
- Keine Aktion - keine Aktion angegeben, es wird eine Warnung bei der Kompilierung ausgegeben
In diesem Beispiel werden beim Löschen eines Unternehmens alle Mitarbeiter gelöscht (Kaskade). Wenn Sie einen Mitarbeiter löschen, wird der Link zu ihm im Unternehmen zurückgesetzt (Vorbildschirm).
Möglichkeiten, dem übergeordneten Element untergeordnete Entitäten hinzuzufügen
1) Die erste Methode ist das Hinzufügen über NSSet. Fügen Sie dem Unternehmen beispielsweise 2 Mitarbeiter hinzu:
let set = NSMutableSet(); if let employee1 = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: moc) as? Employee { employee1.firstName = "" employee1.secondName = "" set.add(employee1) } if let emploee2 = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: moc) as? Employee { employee2.firstName = "" employee2.secondName = "" set.add(employee2) } company.employee = set
Diese Methode eignet sich zum Initialisieren des Objekts oder zum Füllen der Datenbank. Es gibt eine leichte Nuance. Wenn das Unternehmen bereits Mitarbeiter hatte und Sie einen neuen Satz zugewiesen haben, setzen die ehemaligen Mitarbeiter den Link zum Unternehmen zurück, werden jedoch nicht gelöscht. Alternativ können Sie eine Liste der Mitarbeiter abrufen und bereits mit diesem Set arbeiten.
let set = company.mutableSetValue(forKey: "employee")
2) Hinzufügen von untergeordneten Objekten über die übergeordnete ID
if let employee = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: moc) as? Employee { employee.firstName = "" employee.secondName = "" employee.company = company }
Die zweite Methode ist praktisch, wenn Sie ein untergeordnetes Objekt hinzufügen oder bearbeiten
separate Form.
3) Hinzufügen von untergeordneten Objekten über automatisch generierte Methoden
extension Company { @objc(addEmployeeObject:) @NSManaged public func addEmployee(_ value: Employee) @objc(removeEmployeeObject:) @NSManaged public func removeFromEmployee(_ value: Employee) @objc(addEmployee:) @NSManaged public func addEmployee(_ values: NSSet) @objc(removeEmployee:) @NSManaged public func removeFromEmployee(_ values: NSSet) }
Der Vollständigkeit halber ist es nützlich, über diese Methode Bescheid zu wissen, aber irgendwie war sie für mich nicht nützlich, und ich lösche den zusätzlichen Code, um das Projekt nicht zu überladen.
Abfragen untergeordneter Klauseln
In Core Data können Sie keine willkürliche Abfrage zwischen Daten durchführen, wie dies in SQL möglich ist. Zwischen abhängigen Objekten ist es jedoch einfach, mithilfe eines Standardprädikats zu verfolgen. Unten finden Sie ein Beispiel für eine Abfrage, bei der alle Unternehmen ausgewählt werden, in denen sich ein Mitarbeiter mit dem angegebenen Namen befindet:
public static func getCompanyWithEmployee(name: String) -> [Company] { let request = NSFetchRequest<NSFetchRequestResult>(entityName: self.className()) request.predicate = NSPredicate(format: "ANY employee.firstName = %@", name) do { if let result = try moc.fetch(request) as? [Company] { return result } } catch { } return [Company]() }
Der Methodenaufruf im Code sieht folgendermaßen aus:
Verwenden Sie keine Transitfelder in Abfragen, da deren Werte zum Zeitpunkt der Ausführung der Abfrage nicht definiert sind. Es tritt kein Fehler auf, aber das Ergebnis ist falsch.
Attribute einstellen (Felder)
Sie haben wahrscheinlich bemerkt, dass Entitätsattribute mehrere Optionen haben.
Mit optional ist alles aus dem Namen ersichtlich.
Die Option zur Verwendung des Skalartyps wurde in swif angezeigt. Objective-C verwendet keine Skalartypen für Attribute, da diese nicht Null sein können. Der Versuch, einem Attribut über KVC einen Skalarwert zuzuweisen, führt zu einem Laufzeitfehler. Daraus wird deutlich, warum die Attributtypen in Core Data nicht streng mit den Sprachtypen übereinstimmen. In schnellen und gemischten Projekten können skalare Typattribute verwendet werden.
Transitattribute sind berechnete Felder, die
nicht in der Datenbank gespeichert sind. Sie können zur Verschlüsselung verwendet werden. Diese Attribute erhalten Werte über einen überschriebenen Accessor oder durch Zuweisen von Grundelementen nach Bedarf (z. B. überschriebenes willSave und awakeFromFetch).
Attribut-Accessoren:
Wenn Sie keine berechneten Felder verwenden müssen, um beispielsweise eine Verschlüsselung oder etwas anderes durchzuführen, müssen Sie nicht darüber nachdenken, was Attributzubehör ist. In der Zwischenzeit sind die Operationen zum Abrufen und Zuweisen von Werten zu Attributen nicht „atomar“. Um zu verstehen, was ich meine, lesen Sie den folgenden Code:
Verwenden Sie in NSManagedObject-Ereignissen Primitive anstelle der üblichen Zuweisung, um Schleifen zu vermeiden. Ein Beispiel:
override func willSave() { let nameP = encrypt(field: primitiveValue(forKey: "name"), password: password) setPrimitiveValue(nameP, forKey: "nameC") super.willSave() } override func awakeFromFetch() { let nameP = decrypt(field: primitiveValue(forKey: "nameC"), password: password) setPrimitiveValue(nameP, forKey: "name") super.awakeFromFetch() }
Wenn Sie plötzlich die Funktion awakeFromFetch in ein Projekt einschrauben müssen, werden Sie überrascht sein, dass es sehr seltsam funktioniert, aber tatsächlich wird es überhaupt nicht aufgerufen, wenn Sie die Anforderung ausführen. Dies liegt an der Tatsache, dass Core Data über einen sehr intelligenten Caching-Mechanismus verfügt. Wenn sich die Auswahl bereits im Speicher befindet (z. B. weil Sie gerade diese Tabelle ausgefüllt haben), wird die Methode nicht aufgerufen. Meine Experimente haben jedoch gezeigt, dass Sie sich in Bezug auf berechnete Werte sicher auf die Verwendung von awakeFromFetch verlassen können, wie in der Dokumentation von Apple angegeben. Wenn Sie zum Testen und Entwickeln awakeFromFetch erzwingen müssen, fügen Sie manageObjectContext.refreshAllObjects () vor der Anforderung hinzu.
Das ist alles.
Vielen Dank an alle, die bis zum Ende gelesen haben.