Wie wende ich den Typ auf eine NSFetchRequest-Instanz an?

102

In Swift 2 funktionierte der folgende Code:

let request = NSFetchRequest(entityName: String)

aber in Swift 3 gibt es Fehler:

Der generische Parameter "ResultType" konnte nicht abgeleitet werden

weil NSFetchRequestist jetzt ein generischer Typ. In ihren Dokumenten haben sie Folgendes geschrieben:

let request: NSFetchRequest<Animal> = Animal.fetchRequest

Wenn meine Ergebnisklasse zum Beispiel ist, Levelwie soll ich dann richtig anfordern?

Weil das nicht funktioniert:

let request: NSFetchRequest<Level> = Level.fetchRequest
Deniss
quelle
1
Link zu neuen Funktionen, wo ich den Code gefunden habe: developer.apple.com/library/prerelease/content/releasenotes/…
Deniss
1
Es ist eine Methode, so sollte es seinlet request: NSFetchRequest<Level> = Level.fetchRequest()
Sulthan
5
Oder einfachlet request = Level.fetchRequest()
Martin R
2
@MartinR Das würde die Kompilierung nicht bestehen, weil es nicht eindeutig ist.
Sulthan
6
@MartinR scheint, dass Stack Overflow-Mitglieder dich sehr lieben. Sie werden dich blind abstimmen. : P
BangOperator

Antworten:

129
let request: NSFetchRequest<NSFetchRequestResult> = Level.fetchRequest()

oder

let request: NSFetchRequest<Level> = Level.fetchRequest()

je nachdem welche version du willst.

Sie müssen den generischen Typ angeben, da der Methodenaufruf ansonsten nicht eindeutig ist.

Die erste Version ist definiert für NSManagedObject, die zweite Version wird automatisch für jedes Objekt mit einer Erweiterung generiert, z.

extension Level {
    @nonobjc class func fetchRequest() -> NSFetchRequest<Level> {
        return NSFetchRequest<Level>(entityName: "Level");
    }

    @NSManaged var timeStamp: NSDate?
}

Der springende Punkt ist, die Verwendung von String-Konstanten zu entfernen.

Sulthan
quelle
1
Muss ich für jede Entität einen Erweiterungscode hinzufügen? Oder passiert das automatisch? Wenn ich also eine Entität "Hund" und eine Entität "Katze" habe, brauche ich "Erweiterung Hund {@nonobjc ...}" und "Erweiterung Katze {@nonobjc ...}"?
Dave G
@ DaveG Diese Erweiterung wird automatisch für Sie generiert.
Sulthan
1
Okay, ty, aber ich bin ein bisschen verwirrt, weil ich versucht habe 'let fetchRequest = NSFetchRequest <myEntityName> (entityName: "myEntityName")' und den Fehler "Verwendung des nicht deklarierten Typs" myEntityName "
Dave G
4
Hinweis: Die Methode fetchRequest () ist nur in iOS 10 verfügbar
dzensik
@ Sulthan Hallo, als ich es mit deinem Code versucht habe, tritt der folgende Fehler auf. Type 'Project Name' does not conform to protocol 'NSFetchRequestResult'
Svetoslav Atanasov
56

Ich glaube, ich habe es so gemacht:

let request:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Level")

Zumindest werden Daten aus der Datenbank gespeichert und geladen.

Aber es fühlt sich so an, als wäre es keine richtige Lösung, aber es funktioniert vorerst.

Deniss
quelle
Diese Lösung gefällt mir besser, da ich früher eine einzige Methode hatte, die den Entitätsnamen als Parameter verwendete und nur ein Array von NSManagedObjects zurückgab.
n_b
Das hat mir auch gefallen, weil es nicht erforderlich war, eine benutzerdefinierte Klasse zu erstellen. Könnte nur den Entitätsnamen verwenden!
Liam Bolling
Unterschätzte Antwort. Vielen Dank!
David Chelidze
33

Die einfachste Struktur, die ich in 3.0 gefunden habe, lautet wie folgt:

let request = NSFetchRequest<Country>(entityName: "Country")

Dabei ist der Datenentitätstyp Land.

Beim Versuch, eine Core Data BatchDeleteRequest zu erstellen, stellte ich jedoch fest, dass diese Definition nicht funktioniert und Sie anscheinend das folgende Formular verwenden müssen:

let request: NSFetchRequest<NSFetchRequestResult> = Country.fetchRequest()

obwohl die Formate ManagedObject und FetchRequestResult äquivalent sein sollen.

Ron Diel
quelle
1
Die erste in dieser Antwort erwähnte Struktur ist die einzige Möglichkeit, diese derzeit mit meinem abgerufenen Ergebnis-Controller unter Swift3 / iOS 10 / Xcode 8 zu kompilieren.
David L
Das war meine Erfahrung, nachdem ich verschiedene Formen ausprobiert hatte. Haben sie andere Formulare in der CoreData-Präsentation behandelt? Planen Sie es morgen zu überprüfen ...
Ron Diel
Das erste Beispiel ist der einfachste Weg, den ich gefunden habe, ohne die if #available(iOS 10.0) { ... }Bedingung verwenden zu müssen
djv
12

Hier sind einige generische CoreData-Methoden, die Ihre Frage beantworten könnten:

import Foundation
import Cocoa

func addRecord<T: NSManagedObject>(_ type : T.Type) -> T
{
    let entityName = T.description()
    let context = app.managedObjectContext
    let entity = NSEntityDescription.entity(forEntityName: entityName, in: context)
    let record = T(entity: entity!, insertInto: context)
    return record
}

func recordsInTable<T: NSManagedObject>(_ type : T.Type) -> Int
{
    let recs = allRecords(T.self)
    return recs.count
}


func allRecords<T: NSManagedObject>(_ type : T.Type, sort: NSSortDescriptor? = nil) -> [T]
{
    let context = app.managedObjectContext
    let request = T.fetchRequest()
    do
    {
        let results = try context.fetch(request)
        return results as! [T]
    }
    catch
    {
        print("Error with request: \(error)")
        return []
    }
}

func query<T: NSManagedObject>(_ type : T.Type, search: NSPredicate?, sort: NSSortDescriptor? = nil, multiSort: [NSSortDescriptor]? = nil) -> [T]
{
    let context = app.managedObjectContext
    let request = T.fetchRequest()
    if let predicate = search
    {
        request.predicate = predicate
    }
    if let sortDescriptors = multiSort
    {
        request.sortDescriptors = sortDescriptors
    }
    else if let sortDescriptor = sort
    {
        request.sortDescriptors = [sortDescriptor]
    }

    do
    {
        let results = try context.fetch(request)
        return results as! [T]
    }
    catch
    {
        print("Error with request: \(error)")
        return []
    }
}


func deleteRecord(_ object: NSManagedObject)
{
    let context = app.managedObjectContext
    context.delete(object)
}

func deleteRecords<T: NSManagedObject>(_ type : T.Type, search: NSPredicate? = nil)
{
    let context = app.managedObjectContext

    let results = query(T.self, search: search)
    for record in results
    {
        context.delete(record)
    }
}

func saveDatabase()
{
    let context = app.managedObjectContext

    do
    {
        try context.save()
    }
    catch
    {
        print("Error saving database: \(error)")
    }
}

Angenommen, es gibt ein NSManagedObject-Setup für Contact wie folgt:

class Contact: NSManagedObject
{
    @NSManaged var contactNo: Int
    @NSManaged var contactName: String
}

Diese Methoden können folgendermaßen verwendet werden:

let name = "John Appleseed"

let newContact = addRecord(Contact.self)
newContact.contactNo = 1
newContact.contactName = name

let contacts = query(Contact.self, search: NSPredicate(format: "contactName == %@", name))
for contact in contacts
{
    print ("Contact name = \(contact.contactName), no = \(contact.contactNo)")
}

deleteRecords(Contact.self, search: NSPredicate(format: "contactName == %@", name))

recs = recordsInTable(Contact.self)
print ("Contacts table has \(recs) records")

saveDatabase()
iphaaw
quelle
Sauber und elegant. Ich wünschte, ich könnte dies bis 100 abstimmen! Bei einer Nachbesserung habe ich mich gefragt, was Sie denken. Ich habe jede Methode mit dem Kontext? .Perform ({}) für die Thread-Sicherheit versehen. Dies wird von Apple empfohlen.
Tinkerbell
Nicht sehr OO. Wenn Sie diese nicht als Erweiterung von NSManagedObjectContect schreiben könnten, wäre dies eine gute Lösung.
Muz die Axt
1
Gerade bemerkt - um alle Datensätze zu zählen, die Sie abrufen, und dann die Anzahl der Array-Einträge zu zählen - ist dies wirklich ineffizient. Sie möchten wahrscheinlich die Funktion recordsInTable erweitern, um context.count (request) zu verwenden
muz the ax
Dies sind nette Ergänzungen und sollten mehr Stimmen haben, aber wahrscheinlich nicht, weil es ein Exkurs weg von der Hauptfrage ist (obwohl es nützlich ist). Etwas, das ich nur schwer mit der Löschfunktion ändern möchte, ist das Löschen mit der Funktion NSManagedObjectID. Also vor dem context.delete(record)Hinzufügen let record = context.object(with: record.objectID)und Verwenden dieses Datensatzobjekts zum Löschen.
PostCodeism
6

Dies ist der einfachste Weg, um auf Swift 3.0 zu migrieren. Fügen Sie einfach hinzu <Country>

(getestet und gearbeitet)

let request = NSFetchRequest<Country>(entityName: "Country")
Letanthang
quelle
0

Ich hatte auch "ResultType" konnte nicht Fehler abgeleitet werden. Sie wurden gelöscht, sobald ich das Datenmodell neu erstellt und den Codegen jeder Entität auf "Klassendefinition" gesetzt habe. Ich habe hier eine kurze Beschreibung mit schrittweisen Anweisungen gemacht:

Suchen Sie nach einem übersichtlichen Tutorial zum überarbeiteten NSPersistentContainer in Xcode 8 mit Swift 3

Mit "neu erstellt" meine ich, dass ich eine neue Modelldatei mit neuen Einträgen und Attributen erstellt habe. Ein bisschen langweilig, aber es hat funktioniert!

Michael Garito
quelle
0

Was bisher für mich am besten funktioniert hat war:

let request = Level.fetchRequest() as! NSFetchRequest<Level>
benhofmann
quelle
0

Ich hatte das gleiche Problem und löste es mit den folgenden Schritten:

  • Wählen Sie Ihre xcdatamodeld-Datei aus und rufen Sie den Data Model Inspector auf
  • Wählen Sie Ihre erste Entität aus und gehen Sie zur Abschnittsklasse
  • Stellen Sie sicher, dass Codegen "Klassendefinition" ausgewählt ist.
  • Entfernen Sie alle generierten Entitätsdateien. Du brauchst sie nicht mehr.

Danach musste ich alle Vorkommen von fetchRequest entfernen / neu schreiben, da XCode sich irgendwie mit der codegenerierten Version zu vermischen scheint.

HTH

Oliver Koehler
quelle
0

Swift 3.0 Das sollte funktionieren.

let request: NSFetchRequest<NSFetchRequestResult> = NSManagedObject.fetchRequest()
request.entity = entityDescription(context)
request.predicate = predicate
Chamath Jeevan
quelle