Comment appliquer le type à une instance NSFetchRequest?

102

Dans Swift 2, le code suivant fonctionnait:

let request = NSFetchRequest(entityName: String)

mais dans Swift 3, cela donne une erreur:

Le paramètre générique "ResultType" n'a pas pu être déduit

parce que NSFetchRequestc'est maintenant un type générique. Dans leurs documents, ils ont écrit ceci:

let request: NSFetchRequest<Animal> = Animal.fetchRequest

donc si ma classe de résultat est par exemple Levelcomment dois-je demander correctement?

Parce que cela ne fonctionne pas:

let request: NSFetchRequest<Level> = Level.fetchRequest
Deniss
la source
1
lien vers les nouvelles fonctionnalités, où j'ai trouvé le code: developer.apple.com/library/prerelease/content/releasenotes/…
Deniss
1
C'est une méthode, donc ça devrait êtrelet request: NSFetchRequest<Level> = Level.fetchRequest()
Sulthan
5
Ou justelet request = Level.fetchRequest()
Martin R
2
@MartinR Cela ne passerait pas la compilation parce que c'est ambigu.
Sulthan
6
@MartinR semble que les membres du stack overflow vous aiment beaucoup. Ils vous voteront à l'aveuglette. : P
BangOperator

Réponses:

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

ou

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

selon la version que vous souhaitez.

Vous devez spécifier le type générique car sinon, l'appel de méthode est ambigu.

La première version est définie pour NSManagedObject, la deuxième version est générée automatiquement pour chaque objet utilisant une extension, par exemple:

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

    @NSManaged var timeStamp: NSDate?
}

Le but est de supprimer l'utilisation des constantes String.

Sulthan
la source
1
Donc, pour chaque entité, dois-je ajouter un code d'extension? Ou cela se produit automatiquement? Donc, si j'ai une entité "Dog" et une entité "Cat", ai-je besoin de "extension Dog {@nonobjc ...}" et "extension Cat {@nonobjc ...}"?
Dave G
@DaveG Cette extension est générée automatiquement pour vous.
Sulthan le
1
D'accord, ty, mais je suis un peu confus car j'ai essayé 'let fetchRequest = NSFetchRequest <myEntityName> (entityName: "myEntityName")' et j'ai eu l'erreur "Utilisation du type non déclaré" myEntityName "
Dave G
4
Remarque: la méthode fetchRequest () n'est disponible que dans iOS 10
dzensik
@Sulthan Salut, Lorsque j'ai essayé avec votre code, l'erreur suivante se produit. Type 'Project Name' does not conform to protocol 'NSFetchRequestResult'
Svetoslav Atanasov
56

Je pense que je l'ai fait fonctionner en faisant ceci:

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

au moins, il enregistre et charge les données de DataBase.

Mais il semble que ce n'est pas une solution appropriée, mais cela fonctionne pour le moment.

Deniss
la source
J'aime mieux cette solution, car j'avais l'habitude d'avoir une seule méthode qui prenait le nom de l'entité comme paramètre et venait de renvoyer un tableau de NSManagedObjects.
n_b
J'ai aimé cela aussi car cela ne nécessitait pas de créer une classe personnalisée. Pourrait simplement utiliser le nom de l'entité!
Liam Bolling
Réponse sous-estimée. Merci!
David Chelidze
33

La structure la plus simple que j'ai trouvée et qui fonctionne dans la version 3.0 est la suivante:

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

où le type d'entité de données est le pays.

En essayant de créer un Core Data BatchDeleteRequest, cependant, j'ai trouvé que cette définition ne fonctionne pas et il semble que vous devrez utiliser le formulaire:

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

même si les formats ManagedObject et FetchRequestResult sont supposés être équivalents.

Ron Diel
la source
1
La première structure mentionnée dans cette réponse est la seule façon dont je peux actuellement obtenir cela pour compiler avec mon contrôleur de résultats récupéré sur Swift3 / iOS 10 / Xcode 8.
David L
C'était mon expérience après avoir essayé différentes formes. Ont-ils couvert d'autres formes dans la présentation CoreData? Prévoyez de le vérifier demain ...
Ron Diel
Le premier exemple est le moyen le plus simple que j'ai trouvé sans avoir à utiliser le if #available(iOS 10.0) { ... }conditionnel
djv
12

Voici quelques méthodes CoreData génériques qui pourraient répondre à votre question:

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)")
    }
}

En supposant qu'il existe une configuration NSManagedObject pour Contact comme ceci:

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

Ces méthodes peuvent être utilisées de la manière suivante:

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
la source
Propre et élégant. J'aimerais pouvoir voter pour 100! Une retouche, vous demandant ce que vous en pensez, j'ai enveloppé chaque méthode avec context? .Perform ({}) pour la sécurité des threads. Ceci est recommandé par Apple.
Tinkerbell
Pas très OO. À moins que vous ne puissiez les écrire comme une extension de NSManagedObjectContect, ce serait alors une bonne solution.
muz the ax
1
Juste remarqué - pour compter tous les enregistrements, vous les récupérez puis comptez le nombre d'entrées de tableau - c'est vraiment inefficace. Vous voudrez probablement étendre la fonction recordsInTable pour utiliser context.count (request)
muz the ax
Ce sont de bons ajouts et devraient avoir plus de votes, mais probablement pas parce que c'est une digression loin de la question principale (même si c'est utile). Quelque chose que je suggérerais difficilement de changer avec la fonction de suppression, c'est de supprimer avec le NSManagedObjectID. Donc, avant d' context.delete(record)ajouter let record = context.object(with: record.objectID)et d'utiliser cet objet d'enregistrement pour supprimer.
PostCodeism
6

C'est le moyen le plus simple de migrer vers Swift 3.0, il suffit d'ajouter <Country>

(testé et travaillé)

let request = NSFetchRequest<Country>(entityName: "Country")
letanthang
la source
0

J'ai également eu "ResultType" ne pouvait pas être des erreurs déduites. Ils ont été effacés une fois que j'ai reconstruit le modèle de données en définissant Codegen de chaque entité sur "Définition de classe". J'ai fait un bref résumé avec des instructions étape par étape ici:

Vous recherchez un tutoriel clair sur le NSPersistentContainer révisé dans Xcode 8 avec Swift 3

Par «reconstruit», je veux dire que j'ai créé un nouveau fichier de modèle avec de nouvelles entrées et attributs. Un peu fastidieux, mais ça a marché!

Michael Garito
la source
0

Ce qui a fonctionné le mieux pour moi jusqu'à présent était:

let request = Level.fetchRequest() as! NSFetchRequest<Level>
Benhofmann
la source
0

J'ai eu le même problème et je l'ai résolu avec les étapes suivantes:

  • Sélectionnez votre fichier xcdatamodeld et accédez à l'inspecteur de modèle de données
  • Sélectionnez votre première entité et accédez à la classe Section
  • Assurez-vous que Codegen "Définition de classe" est sélectionné.
  • Supprimez tous vos fichiers Entity générés. Vous n'en avez plus besoin.

Après cela, j'ai dû supprimer / réécrire toutes les occurrences de fetchRequest car XCode semble en quelque sorte se mélanger avec la version générée par le code.

HTH

Oliver Koehler
la source
0

Swift 3.0 Cela devrait fonctionner.

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