swift - 无法推断通用参数 T

标签 swift generics swift3

我正在尝试为我的应用程序创建一个generic 存储,其中可以存储Serializable 项目。

我有一些结构实现了Serializable protocol

 protocol Serializable {
    func serialize() -> [String: AnyObject]
    init?(byDeserializing dictionary : [String: AnyObject])
}

这是我的存储协议(protocol)

protocol Storage {
    func getItems<T:Serializable>(completion : @escaping ([T]?)-> Void )
    func save<T:Serializable>(_ items : [T], completion : @escaping (Bool)-> Void )
}

extension Storage  {

    func data<T:Serializable>(from serializableItems : [T]) -> Data? {

        var serializedItems = [Dictionary<String,AnyObject>]()

        for item in serializableItems {
            serializedItems.append(item.serialize())
        }

        guard let serializedData = try? PropertyListSerialization.data(fromPropertyList: serializedItems, format:.binary, options:0) else {
            return nil;
        }
        return serializedData

    }

     func serializedItems(from data : Data) -> [Dictionary<String, AnyObject>]? {

        guard  let serilizedItems = try? PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil) as? [Dictionary<String,AnyObject>] else {
            return nil
        }
        return serilizedItems
    }

    func deserialize<T:Serializable>(from serializedItems: [[String : AnyObject]]  ) -> [T] {
        var items = [T]()
        for serializedItem in serializedItems {
            if let item = T(byDeserializing:serializedItem){
                items.append(item)
            }
        }
        return items
    }
}

当应用程序想要恢复存储的项目时,它只需调用 self.storage.getItems....

func getItems<T : Serializable>(completion: @escaping ([T]?) -> Void) {
        let path = fileURL().path

        concurrentQueue.async {
            guard let serializedItems = NSArray(contentsOfFile: path) as? [[String : AnyObject]], serializedItems.count > 0 else {
                completion(nil)
                return
            }

            let deserializedItems = self.deserialize(from: serializedItems)
            completion(deserializedItems)
        }
    }

我调用了 getItems 方法,我在两个存储协调器中都遇到了这个编译错误

PlistStorageCoordinator

Generic Error

UserDefaultrsStorageCoordinator

Generic inferred error 在我将泛型添加到此方法之前,它工作得很好。有谁知道哪里出了问题?

我不知道为什么,但这解决了它。我不喜欢它,因为我在两个存储中复制代码。有人可以给我解释一下吗?

 func getItems<T : Serializable>(completion: @escaping ([T]?) -> Void) {

        concurrentQueue.async {
            guard  let data = self.userDefaults.data(forKey: self.modelKey), let serializedItems = self.serializedItems(from: data), serializedItems.count > 0 else {
                completion(nil)
                return
            }
            var items = [T]()
            for serializedItem in serializedItems {
                if let item = T(byDeserializing:serializedItem){
                    items.append(item)
                }
            }
            completion(items)
        }
    }

最佳答案

这个协议(protocol)并不像你想的那样:

protocol Serializable {
    func serialize() -> Dictionary<String, AnyObject>
    static func deserialize<T>(_ dictionary : Dictionary<String,AnyObject>) -> T
}

这表示一个 Serializable 可以序列化为一个字典,并且任何 Serializable 类型都有一个静态方法可以将一个字典转换成东西 (T)。那个“东西”没有任何 promise 。它与 Serializable 类型无关。编译器无法猜测此类型,除非查看您请求的返回值是什么。

几乎可以肯定,您的意思是 Serializable 可以从字典中反序列化:

protocol Serializable {
    func serialize() -> Dictionary<String, AnyObject>
    static func deserialize(_ dictionary : [String: AnyObject]) -> Self
}

这说明了您的意思,但几乎不可能以不会崩溃的方式实现。如果字典不包含您期望的键怎么办?那你返回什么?这个方法应该是可选的或抛出的,并且它作为一个 init 会更快捷。例如:

protocol Serializable {
    func serialize() -> [String: AnyObject]
    init?(byDeserializing dictionary: [String: AnyObject])
}

有了它,您的系统的更多部分将按照您的预期运行。

(总而言之,一定要看看 NSCoding,它已经以更强大的方式完成了您想要做的事情。有理由不使用 NSCoding,但要确保它是一个积极的选择,而不是仅仅重新发明它。)


这个协议(protocol)也没有说出你想表达的意思:

protocol Storage {
    func getItems<T:Serializable>(completion : @escaping ([T]?)-> Void )
    func save<T:Serializable>(_ items : [T], completion : @escaping (Bool)-> Void )
}

这表示 Storage 可以返回任何可序列化类型的项目列表,并且可以保存任何可序列化类型的项目列表。这些类型不必以任何方式相关。您的意思似乎是存储可以获取并保存与该存储关联的特定类型的项目。在这种情况下,您需要一个关联类型:

protocol Storage {
    associatedType Element
    func getItems(completion : @escaping ([Element]?)-> Void )
    func save(_ items : [Element], completion : @escaping (Bool)-> Void )
}

这个函数:

func getItems<T:Serializable>(completion : @escaping ([T]?)-> Void )

有两个参数。第二个参数你大概明白了。它是 completion,它是一个接受可选数组并返回 Void 的函数。但我相信您误解了第一个参数:T。当您调用 getItems 时,您将隐式传递一个 type 作为参数之一。每次调用 getItems 时,您都可以传递一个不同的 T(就像您可以传递一个不同的 completion 一样。 没有任何意义T 将它与这个 Storable 联系起来。这就是泛型的工作方式。你想要的是一种 与 Storable 联系在一起的类型,并且在 Storable 中的所有方法之间保持一致。这是一个关联类型。

关于swift - 无法推断通用参数 T,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40221317/

相关文章:

ios - swift 3 : UIViewControllerTransitioningDelegate stopped working

database - 为我的 Swift 解析表添加值(value)

ios - Swift 5.1 运行时如何与旧版本的 iOS 一起工作?

c# - 如何测试一个空的 generic.dictionary 集合?

java - 如何获取对当前类实例的编译时引用

ios - 具有重复和延迟的 animateKeyframes 无法按预期工作

ios - 如何在 Swift 中将绘图保存到 tableview 中?

ios - CellForRowAtIndexPath 被意外调用

java - 为什么这里不进行隐式转换?

ios - 根据属性类型转换数据时出现 NSNumber 异常