Как применить тип к экземпляру NSFetchRequest?

102

В Swift 2 работал следующий код:

let request = NSFetchRequest(entityName: String)

но в Swift 3 это дает ошибку:

Общий параметр ResultType не может быть выведен

потому что NSFetchRequestтеперь это общий тип. В своих документах они написали следующее:

let request: NSFetchRequest<Animal> = Animal.fetchRequest

Итак, если мой класс результатов, например, Levelкак мне правильно запросить?

Потому что это не работает:

let request: NSFetchRequest<Level> = Level.fetchRequest
Денис
источник
1
ссылка на новые функции, где я нашел код: developer.apple.com/library/prerelease/content/releasenotes/…
Денисс,
1
Это метод, так и должно бытьlet request: NSFetchRequest<Level> = Level.fetchRequest()
Султан
5
Или простоlet request = Level.fetchRequest()
Martin R
2
@MartinR Это не пройдет компиляцию, потому что это неоднозначно.
Sulthan
6
@MartinR кажется, что участники переполнения стека вас очень любят. Они будут слепо голосовать за вас. : P
BangOperator

Ответы:

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

или

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

в зависимости от того, какую версию вы хотите.

Вы должны указать универсальный тип, потому что в противном случае вызов метода будет неоднозначным.

Первая версия определена для NSManagedObject, вторая версия создается автоматически для каждого объекта с использованием расширения, например:

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

    @NSManaged var timeStamp: NSDate?
}

Все дело в том, чтобы отказаться от использования строковых констант.

Султан
источник
1
Итак, нужно ли мне добавлять код расширения для каждой сущности? Или это происходит автоматически? Итак, если у меня есть сущность «Собака» и сущность «Кошка», нужны ли мне «расширение Dog {@nonobjc ...}» и «расширение Cat {@nonobjc ...}»?
Dave G
@DaveG Это расширение создается для вас автоматически.
Sulthan 05
1
Хорошо, ты, но я немного сбит с толку, потому что я попробовал 'let fetchRequest = NSFetchRequest <myEntityName> (entityName: "myEntityName")' и получил сообщение об ошибке «Использование необъявленного типа« myEntityName »
Дэйв Дж.
4
Примечание. Метод fetchRequest () доступен только в iOS 10
dzensik
@Sulthan Привет! Когда я попытался использовать ваш код, возникла следующая ошибка. Type 'Project Name' does not conform to protocol 'NSFetchRequestResult'
Светослав Атанасов
56

Я думаю, что у меня это получилось, сделав это:

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

по крайней мере, он сохраняет и загружает данные из базы данных.

Но кажется, что это неправильное решение, но пока оно работает.

Денис
источник
Мне больше нравится это решение, так как у меня был единственный метод, который принимал имя объекта в качестве параметра и просто возвращал массив NSManagedObjects.
n_b
Это тоже понравилось, потому что не требовалось создавать собственный класс. Можно просто использовать имя объекта!
Лиам Боллинг
Недооцененный ответ. Спасибо!
Давид Челидзе
33

Самая простая структура, которая работает в версии 3.0, выглядит следующим образом:

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

где Тип объекта данных - Страна.

Однако при попытке создать Core Data BatchDeleteRequest я обнаружил, что это определение не работает, и, похоже, вам понадобится форма:

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

хотя предполагается, что форматы ManagedObject и FetchRequestResult эквивалентны.

Рон Дил
источник
1
Первая структура, упомянутая в этом ответе, является единственным способом, которым я могу в настоящее время скомпилировать ее с помощью моего полученного контроллера результатов на Swift3 / iOS 10 / Xcode 8.
Дэвид Л.
Это был мой опыт после того, как я попробовал разные формы. Охватывали ли они какие-либо другие формы в презентации CoreData? Планирую проверить это завтра ...
Рон Дил
Первый пример - это самый простой способ, который я нашел без использования if #available(iOS 10.0) { ... }условного
оператора
12

Вот несколько общих методов CoreData, которые могут ответить на ваш вопрос:

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

Предполагая, что для Contact существует такая настройка NSManagedObject:

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

Эти методы можно использовать следующим образом:

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()
Ифао
источник
Чисто и элегантно. Хотел бы я проголосовать за это на 100! Одно касание, интересно, что вы думаете, я обернул каждый метод контекстом? .Perform ({}) для обеспечения безопасности потоков. Это рекомендовано Apple.
Tinkerbell
Не очень ОО. Если вы не смогли написать их как расширение NSManagedObjectContect, это было бы хорошим решением.
muz the axe
1
Только что заметил - подсчитывать все записи, которые вы их извлекаете, а затем подсчитывать количество записей массива - это действительно неэффективно. Вероятно, вы захотите расширить функцию recordsInTable, чтобы использовать context.count (request)
muz the axe
Это хорошие дополнения, и они должны набрать больше голосов, но, вероятно, нет, потому что это отступление от основного вопроса (хотя оно и полезно). Что-то, что я бы посоветовал изменить с помощью функции удаления, - это удалить NSManagedObjectIDвместо этого. Итак, прежде чем context.delete(record)добавлять let record = context.object(with: record.objectID)и использовать этот объект записи для удаления.
PostCodeism 01
6

Это самый простой способ перейти на Swift 3.0, просто добавьте <Country>

(проверено и отработано)

let request = NSFetchRequest<Country>(entityName: "Country")
Letanthang
источник
0

У меня также был "ResultType", о котором нельзя было вывести ошибки. Они очистились, как только я перестроил модель данных, установив для каждого объекта Codegen значение «Определение класса». Я сделал краткую запись с пошаговыми инструкциями здесь:

Ищете четкое руководство по обновленному NSPersistentContainer в Xcode 8 с Swift 3

Под «перестроенной» я подразумеваю, что я создал новый файл модели с новыми записями и атрибутами. Немного утомительно, но это сработало!

Майкл Гарито
источник
0

Пока что лучше всего для меня работало:

let request = Level.fetchRequest() as! NSFetchRequest<Level>
Benhofmann
источник
0

У меня была такая же проблема, и я решил ее, выполнив следующие действия:

  • Выберите файл xcdatamodeld и перейдите в Инспектор моделей данных.
  • Выберите свою первую сущность и перейдите в класс раздела
  • Убедитесь, что выбран Codegen «Определение класса».
  • Удалите все созданные вами файлы Entity. Они тебе больше не нужны.

После этого мне пришлось удалить / переписать все вхождения fetchRequest, поскольку XCode, похоже, каким-то образом смешивается с версией, сгенерированной кодом.

HTH

Оливер Кёлер
источник
0

Swift 3.0 Это должно работать.

let request: NSFetchRequest<NSFetchRequestResult> = NSManagedObject.fetchRequest()
request.entity = entityDescription(context)
request.predicate = predicate
Чамат Дживан
источник