Como aplicar o tipo a uma instância NSFetchRequest?

102

No Swift 2, o seguinte código estava funcionando:

let request = NSFetchRequest(entityName: String)

mas no Swift 3 dá erro:

O parâmetro genérico "ResultType" não pôde ser inferido

porque NSFetchRequestagora é um tipo genérico. Em seus documentos, eles escreveram isto:

let request: NSFetchRequest<Animal> = Animal.fetchRequest

então, se minha classe de resultado for, por exemplo, Levelcomo devo solicitar corretamente?

Porque isso não está funcionando:

let request: NSFetchRequest<Level> = Level.fetchRequest
Deniss
fonte
1
link para novos recursos, onde encontrei o código: developer.apple.com/library/prerelease/content/releasenotes/…
Deniss
1
É um método, então deveria serlet request: NSFetchRequest<Level> = Level.fetchRequest()
Sulthan
5
Ou apenaslet request = Level.fetchRequest()
Martin R de
2
@MartinR Isso não passaria na compilação porque é ambigioso.
Sulthan de
6
@MartinR parece que os membros do stack overflow te amam muito. Eles vão te apoiar cegamente. : P
BangOperator

Respostas:

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

ou

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

dependendo da versão que você deseja.

Você tem que especificar o tipo genérico, porque caso contrário, a chamada do método é ambígua.

A primeira versão é definida para NSManagedObject, a segunda versão é gerada automaticamente para cada objeto usando uma extensão, por exemplo:

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

    @NSManaged var timeStamp: NSDate?
}

O objetivo é remover o uso de constantes String.

Sulthan
fonte
1
Portanto, para cada entidade, preciso adicionar um código de extensão? Ou isso acontece automaticamente? Portanto, se eu tiver uma entidade "Cachorro" e uma entidade "Gato", preciso de "extensão Cachorro {@nonobjc ...}" e "extensão Cat {@nonobjc ...}"?
Dave G
@DaveG Essa extensão é gerada para você automaticamente.
Sulthan
1
Ok, ty, mas estou um pouco confuso porque tentei 'let fetchRequest = NSFetchRequest <myEntityName> (entityName: "myEntityName")' e recebi o erro "Uso do tipo não declarado" myEntityName "
Dave G
4
Nota: O método fetchRequest () está disponível apenas no iOS 10
dzensik
@Sulthan Olá, Quando tentei com seu código, ocorreu o seguinte erro. Type 'Project Name' does not conform to protocol 'NSFetchRequestResult'
Svetoslav Atanasov
56

Acho que consegui fazer isso:

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

pelo menos ele salva e carrega dados do DataBase.

Mas parece que não é uma solução adequada, mas funciona por enquanto.

Deniss
fonte
Gosto mais dessa solução, pois costumava ter um único método que pegava o nome da entidade como parâmetro e apenas passava de volta um array de NSManagedObjects.
n_b
Também gostei disso porque não exigia a criação de uma classe personalizada. Só poderia usar o nome da entidade!
Liam Bolling
Resposta subestimada. Obrigado!
David Chelidze
33

A estrutura mais simples que descobri que funciona no 3.0 é a seguinte:

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

onde o tipo de entidade de dados é o país.

Ao tentar criar um Core Data BatchDeleteRequest, no entanto, descobri que essa definição não funciona e parece que você precisará seguir o formulário:

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

mesmo que os formatos ManagedObject e FetchRequestResult devam ser equivalentes.

Ron Diel
fonte
1
A primeira estrutura mencionada nesta resposta é a única maneira que posso fazer para compilar com meu controlador de resultados buscado no Swift3 / iOS 10 / Xcode 8.
David L
Essa foi minha experiência depois de tentar várias formas. Eles cobriram algum outro formulário na apresentação CoreData? Planeje conferir amanhã ...
Ron Diel
O primeiro exemplo é a maneira mais simples que encontrei sem ter que usar a if #available(iOS 10.0) { ... }condicional
djv
12

Aqui estão alguns métodos CoreData genéricos que podem responder à sua pergunta:

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

Supondo que haja uma configuração NSManagedObject para contato como esta:

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

Esses métodos podem ser usados ​​da seguinte maneira:

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
fonte
Limpo e elegante. Gostaria de poder votar 100! Um retoque, me perguntando o que você acha, envolvi cada método com o contexto? .Perform ({}) para a segurança do thread. Isso é recomendado pela Apple.
Sininho de
Não muito OO. A menos que você pudesse escrevê-los como uma extensão para NSManagedObjectContect, essa seria uma boa solução.
muz the ax
1
Apenas observei - para contar todos os registros, você os está recuperando e depois contando o número de entradas do array - isso é realmente ineficiente. Você provavelmente deseja expandir a função recordsInTable para utilizar context.count (solicitação)
muz the ax
Essas são adições legais e deveriam ter mais votos, mas provavelmente não, porque está uma digressão longe da questão principal (embora seja útil). Algo que eu sugiro que seja difícil mudar com a função de exclusão, é excluir com o NSManagedObjectID. Portanto, antes de context.delete(record)adicionar let record = context.object(with: record.objectID)e usar esse objeto de registro para excluir.
PostCodeism 01 de
6

Esta é a maneira mais simples de migrar para o Swift 3.0, basta adicionar <Country>

(testado e funcionou)

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

Eu também tive "ResultType" não poderia ser erros inferidos. Eles foram limpos assim que eu reconstruí o modelo de dados configurando Codegen de cada entidade para "Definição de Classe". Fiz uma breve redação com instruções passo a passo aqui:

Procurando um tutorial claro sobre o NSPersistentContainer revisado no Xcode 8 com Swift 3

Por "reconstruído" quero dizer que criei um novo arquivo de modelo com novas entradas e atributos. Um pouco entediante, mas funcionou!

Michael Garito
fonte
0

O que funcionou melhor para mim até agora foi:

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

Eu tive o mesmo problema e resolvi com as seguintes etapas:

  • Selecione seu arquivo xcdatamodeld e vá para o Data Model Inspector
  • Selecione sua primeira Entidade e vá para a classe Seção
  • Certifique-se de que Codegen "Definição de classe" está selecionado.
  • Remova todos os seus arquivos Entity gerados. Você não precisa mais deles.

Depois de fazer isso, tive que remover / reescrever todas as ocorrências de fetchRequest, pois o XCode parece de alguma forma se confundir com a versão gerada pelo código.

HTH

Oliver Koehler
fonte
0

Swift 3.0 Isso deve funcionar.

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