当泛型类型通过参数传递时,Swift 无法推断泛型类型 [英] Swift can't infer generic type when generic type is being passed through a parameter

查看:17
本文介绍了当泛型类型通过参数传递时,Swift 无法推断泛型类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在为核心数据编写一个通用的包装类.

I'm writing a generic wrapper class for core data.

以下是我的一些基本类型.没什么特别的.

Here are some of my basic types. Nothing special.

typealias CoreDataSuccessLoad = (_: NSManagedObject) -> Void
typealias CoreDataFailureLoad = (_: CoreDataResponseError?) -> Void
typealias ID = String


enum CoreDataResult<Value> {
    case success(Value)
    case failure(Error)
}

enum CoreDataResponseError : Error {
    typealias Minute = Int
    typealias Key = String
    case idDoesNotExist
    case keyDoesNotExist(key: Key)
    case fetch(entityName: String)
}

我已经在协议中抽象了我的核心数据写入.如果您让我知道您对我试图实现的抽象的评论,我将不胜感激.然而在扩展中我遇到了以下错误:

I've abstracted my coredata writes in a protocol. I'd appreciate if you let me know of your comments about the abstraction I'm trying to pull off. Yet in the extension I run into the following error:

无法将NSFetchRequest"类型的值转换为预期的参数类型 'NSFetchRequest<_>'

Cannot convert value of type 'NSFetchRequest' to expected argument type 'NSFetchRequest<_>'

不确定我该如何修复它.我尝试了更改代码的变体,但没有成功...

Not sure exactly how I can fix it. I've tried variations of changing my code but didn't find success...

protocol CoreDataWriteManagerProtocol {
    associatedtype ManagedObject : NSManagedObject

    var persistentContainer : NSPersistentContainer {get}
    var idName : String {get}
    func loadFromDB(storableClass : ManagedObject.Type, id: ID) throws -> CoreDataResult<ManagedObject>
    func update(storableClass : ManagedObject.Type, id: ID, fields: [String : Any]) throws
    func fetch(request: NSFetchRequest<ManagedObject>, from context: NSManagedObjectContext)
    init(persistentContainer : NSPersistentContainer)
}

extension CoreDataWriteManagerProtocol {
    private func loadFromDB(storableClass : ManagedObject.Type, id: ID) -> CoreDataResult<ManagedObject>{
        let predicate = NSPredicate(format: "%@ == %@", idName, id)

        let fetchRequest : NSFetchRequest = storableClass.fetchRequest()
        fetchRequest.predicate = predicate

        // ERROR at below line!
        return fetch(request: fetchRequest, from: persistentContainer.viewContext) 
    }

    func fetch<ManagedObject: NSManagedObject>(request: NSFetchRequest<ManagedObject>, from context: NSManagedObjectContext) -> CoreDataResult<ManagedObject>{
        guard let results = try? context.fetch(request) else {
            return .failure(CoreDataResponseError.fetch(entityName: request.entityName ?? "Empty Entity Name")) // @TODO not sure if entityName gets passed or not.
        }
        if let result = results.first {
            return .success(result)
        }else{
            return .failure(CoreDataResponseError.idDoesNotExist)
        }
    }
}

另外,如果我改变行:

let fetchRequest : NSFetchRequest = storableClass.fetchRequest()

到:

let fetchRequest : NSFetchRequest<storableClass> = storableClass.fetchRequest()

我收到以下错误:

使用未声明的类型'storableClass'`

Use of undeclared type 'storableClass'`

我的直觉告诉我,编译器无法映射类型参数",即它不理解 storableClass 实际上是一种类型.相反,它只能映射泛型参数或实际类型.因此这是行不通的.

My intuition tells me that the compiler can't map 'parameters that are types' ie it doesn't understand that storableClass is actually a type. Instead it can only map generics parameters or actual types. Hence this doesn't work.

我使用静态方法 Vadian 并写了这个:

I used static approach Vadian and wrote this:

private func create(_ entityName: String, json : [String : Any]) throws -> ManagedObject {

    guard let entityDescription = NSEntityDescription.entity(forEntityName: entityName, in: Self.persistentContainer.viewContext) else {
        print("entityName: (entityName) doesn't exist!")
        throw CoreDataError.entityNotDeclared(name: entityName)
    }

    let _ = entityDescription.relationships(forDestination: NSEntityDescription.entity(forEntityName: "CountryEntity", in: Self.persistentContainer.viewContext)!)
    let relationshipsByName = entityDescription.relationshipsByName

    let propertiesByName = entityDescription.propertiesByName

    guard let managedObj = NSEntityDescription.insertNewObject(forEntityName: entityName, into: Self.persistentContainer.viewContext) as? ManagedObject else {
        throw CoreDataError.entityNotDeclared(name: entityName)
    }

    for (propertyName,_) in propertiesByName {
        if let value = json[propertyName] {
            managedObj.setValue(value, forKey: propertyName)
        }
    }
    // set all the relationships
    guard !relationshipsByName.isEmpty else {
        return managedObj
    }

    for (relationshipName, _ ) in relationshipsByName {
        if let object = json[relationshipName], let objectDict = object as? [String : Any] {
            let entity = try create(relationshipName, json: objectDict)
            managedObj.setValue(entity, forKey: relationshipName)
        }
    }
    return managedObj
}

但是下面的部分不是通用的,因为我用 as 来投射它?管理对象.基本上它不像 Vadian 所说的那样 Swifty:

But the following piece of it is not generic as in I'm casting it with as? ManagedObject. Basically it's not Swifty as Vadian puts it:

guard let managedObj = NSEntityDescription.insertNewObject(forEntityName: entityName, into: Self.persistentContainer.viewContext) as? ManagedObject else {
    throw CoreDataError.entityNotDeclared(name: entityName)
}

有什么办法可以解决吗?

Is there any way around that?

推荐答案

我的建议有点不同.它使用静态方法

My suggestion is a bit different. It uses static methods

NSManagedObject 子类上调用 loadFromDBfetch.好处是总是返回关联的类型而无需任何进一步的类型转换.

Call loadFromDB and fetch on the NSManagedObject subclass. The benefit is that always the associated type is returned without any further type cast.

另一个变化是throw错误.由于 Core Data API 广泛依赖于 throw 错误,我的建议是删除 CoreDataResult.所有错误都通过.成功时返回对象,失败时抛出错误.

Another change is throwing errors. As the Core Data API relies widely on throwing errors my suggestion is to drop CoreDataResult<Value>. All errors are passed through. On success the object is returned, on failure an error is thrown.

我省略了 id 相关代码和 update 方法.你可以添加一个 static func predicate(for id : ID)

I left out the id related code and the update method. You can add a static func predicate(for id : ID)

protocol CoreDataWriteManagerProtocol {
    associatedtype ManagedObject : NSManagedObject = Self

    static var persistentContainer : NSPersistentContainer { get }
    static var entityName : String { get }
    static func loadFromDB(predicate: NSPredicate?) throws -> ManagedObject
    static func fetch(request: NSFetchRequest<ManagedObject>) throws -> ManagedObject
    static func insertNewObject() -> ManagedObject
}

extension CoreDataWriteManagerProtocol where Self : NSManagedObject {

    static var persistentContainer : NSPersistentContainer {
        return (UIApplication.delegate as! AppDelegate).persistentContainer
    }

    static var entityName : String {
        return String(describing:self)
    }

    static func loadFromDB(predicate: NSPredicate?) throws -> ManagedObject {
        let request = NSFetchRequest<ManagedObject>(entityName: entityName)
        request.predicate = predicate
        return try fetch(request: request)
    }

    static func fetch(request: NSFetchRequest<ManagedObject>) throws -> ManagedObject {
        guard let results = try? persistentContainer.viewContext.fetch(request) else {
            throw CoreDataResponseError.fetch(entityName: entityName)
        }
        if let result = results.first {
            return result
        } else {
            throw CoreDataResponseError.idDoesNotExist
        }
    }

    static func insertNewObject() -> ManagedObject {
        return NSEntityDescription.insertNewObject(forEntityName: entityName, into: persistentContainer.viewContext) as! ManagedObject
    }
}

这篇关于当泛型类型通过参数传递时,Swift 无法推断泛型类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆