106

In Swift 2 the following code was working:

let request = NSFetchRequest(entityName: String)

but in Swift 3 it gives error:

Generic parameter "ResultType" could not be inferred

because NSFetchRequest is now a generic type. In their documents they wrote this:

let request: NSFetchRequest<Animal> = Animal.fetchRequest

so if my result class is for example Level how should I request correctly?

Because this not working:

let request: NSFetchRequest<Level> = Level.fetchRequest
Braiam
  • 1
  • 11
  • 47
  • 78
Deniss
  • 1,675
  • 2
  • 12
  • 13

10 Answers10

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

or

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

depending which version you want.

You have to specify the generic type because otherwise the method call is ambiguous.

The first version is defined for NSManagedObject, the second version is generated automatically for every object using an extension, e.g:

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

    @NSManaged var timeStamp: NSDate?
}

The whole point is to remove the usage of String constants.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • 1
    So for every entity, do I need to add extension code? Or that happens automatically? So if I have a "Dog" entity and "Cat" entity, do I need "extension Dog { @nonobjc... }" and "extension Cat { @nonobjc...}"? – Dave G Sep 05 '16 at 14:45
  • @DaveG That extension is generated for you automatically. – Sulthan Sep 05 '16 at 15:22
  • 1
    Okay, ty, but I'm a bit confused because I tried 'let fetchRequest = NSFetchRequest(entityName: "myEntityName")' and I got the error "Use of undeclared type "myEntityName" – Dave G Sep 05 '16 at 15:25
  • 4
    Note: The method fetchRequest() is only available in iOS 10 – dzensik Sep 15 '16 at 15:18
  • @Sulthan Hi, When I tried with your code, the following error occurs. `Type 'Project Name' does not conform to protocol 'NSFetchRequestResult'` – Ioan Moldovan Oct 03 '16 at 14:13
  • @DavidJonker In most situations you want the second option. – Sulthan Oct 03 '16 at 14:19
  • @Sulthan i tried with second option `let request: NSFetchRequest = Level.fetchRequest()` but the error occurs. – Ioan Moldovan Oct 03 '16 at 14:22
  • @DavidJonker Please, ask a question. I cannot help you with specific problems in comments. – Sulthan Oct 03 '16 at 15:53
  • @DavidJonker Did you find any solution of let request: NSFetchRequest = Level.fetchRequest() i tried out the sample but it gives me error "Use of undeclared type 'Entity Name'" – Kirti Parghi Nov 18 '16 at 09:56
  • @KirtiParghi For me, at first it gave the error of "use of undeclared type". It was fixed by adding 'import CoreData' at the top and create a subclass for the entity – Ramtin Nov 22 '16 at 21:01
  • @dzensik is that true? I can run against iOS 9 and 8 without problem as long as it is swift 3. – CodeBrew Feb 21 '17 at 22:21
  • I'm trying the same solution but I get error says "Use of unresolved identifier 'NSFetchRequest' in swift 4, is there any import required? – Niloufar Sep 01 '18 at 14:13
  • @Niloufar `CoreData` usually. – Sulthan Sep 01 '18 at 15:32
56

I think i got it working by doing this:

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

at least it saves and loads data from DataBase.

But it feels like it is not a proper solution, but it works for now.

Deniss
  • 1,675
  • 2
  • 12
  • 13
  • I like this solution better, as I used to have a single method that took the entity name as a parameter and just passed back an array of NSManagedObjects. – n_b Sep 20 '16 at 08:02
  • Liked this too because it didn't require creating a custom class. Could just use entity name! – Liam Bolling Nov 27 '16 at 10:50
33

The simplest structure I found that works in 3.0 is as follows:

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

where the data entity Type is Country.

When trying to create a Core Data BatchDeleteRequest, however, I found that this definition does not work and it seems that you'll need to go with the form:

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

even though the ManagedObject and FetchRequestResult formats are supposed to be equivalent.

Ron Diel
  • 1,354
  • 7
  • 10
  • 1
    The first structure mentioned in this answer is the only way I can currently get this to compile with my fetched results controller on Swift3 / iOS 10 / Xcode 8. – David L Jun 19 '16 at 20:34
  • That was my experience after trying various forms. Did they cover any other forms in the CoreData presentation? Plan to check it out tomorrow... – Ron Diel Jun 19 '16 at 22:53
  • The first example is the simplest way I've found without having to use the `if #available(iOS 10.0) { ... }` conditional – djv Sep 29 '16 at 02:22
15

Here are some generic CoreData methods that might answer your 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)")
    }
}

Assuming that there is a NSManagedObject setup for Contact like this:

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

These methods can be used in the following way:

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
  • 6,764
  • 11
  • 58
  • 83
  • Clean and elegant. Wish I could vote this up 100! One touchup, wondering what you think, I wrapped each method with context?.perform({}) for thread safety. This is recommended by Apple. – Tinkerbell Nov 29 '16 at 09:24
  • Not very OO. Unless you were able to write these as an extension to NSManagedObjectContect, that would then be a nice solution. – muz the axe Jun 26 '17 at 05:28
  • 2
    Just noticed - to count all of the records you are retrieving them and then counting the number of array entries - this is really inefficient. You probably want to expand the recordsInTable function to utilise context.count(request) – muz the axe Jun 26 '17 at 05:31
  • These are nice additions and should have more votes up, but probably doesn't because it's a digression away from the main question (even though it's useful). Something I would suggest hard on to change with the delete function, is to delete with the `NSManagedObjectID` instead. So before `context.delete(record)` add in `let record = context.object(with: record.objectID)` and use that record object to delete. – PostCodeism Dec 01 '18 at 21:18
6

This is the simplest way to migrate to Swift 3.0, just add <Country>

(tested and worked)

let request = NSFetchRequest<Country>(entityName: "Country")
letanthang
  • 390
  • 5
  • 7
1

Swift 3.0 This should work.

let request: NSFetchRequest<NSFetchRequestResult> = NSManagedObject.fetchRequest()
request.entity = entityDescription(context)
request.predicate = predicate
Chamath Jeevan
  • 5,072
  • 1
  • 24
  • 27
0

I also had "ResultType" could not be inferred errors. They cleared once I rebuilt the data model setting each entity's Codegen to "Class Definition". I did a brief writeup with step by step instructions here:

Looking for a clear tutorial on the revised NSPersistentContainer in Xcode 8 with Swift 3

By "rebuilt" I mean that I created a new model file with new entries and attributes. A little tedious, but it worked!

Community
  • 1
  • 1
Michael Garito
  • 3,005
  • 1
  • 11
  • 11
0

What worked best for me so far was:

let request = Level.fetchRequest() as! NSFetchRequest<Level>
benhofmann
  • 331
  • 3
  • 12
0

I had the same issue and I solved it with the following steps:

  • Select your xcdatamodeld file and go to the Data Model Inspector
  • Select your first Entity and go to Section class
  • Make sure that Codegen "Class Definition" is selected.
  • Remove all your generated Entity files. You don't need them anymore.

After doing that I had to remove/rewrite all occurences of fetchRequest as XCode seem to somehow mix up with the codegenerated version.

HTH

Oliver Koehler
  • 711
  • 2
  • 8
  • 24
0
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

func loadItemsCategory() {

    let request: NSFetchRequest<Category> = Category.fetchRequest()
    
    do {
        categoryArray = try context.fetch(request)
    } catch {
        print(error)
    }
    
    tableView.reloadData()
    
}
jeka
  • 1
  • 1