Не удалось определить общий параметр T

#swift #generics #swift3

#swift #общие параметры #swift3

Вопрос:

Я пытаюсь создать generic хранилище для моего приложения, где Serializable могли бы храниться элементы.

У меня есть несколько структур, которые реализуют Serializable protocol

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

Это мой Storage 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
  

Общая ошибка

 UserDefaultrsStorageCoordinator
  

Ошибка общего вывода
Он работал отлично, пока я не добавил общие параметры к этому методу. Кто-нибудь знает, что может быть не так?

Я не знаю почему, но это исправляет это. Мне это не нравится, потому что я дублирую код в обоих хранилищах. Кто-нибудь может мне это объяснить?

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

Комментарии:

1. Как вы определили self.storage . Из-за ошибки я думаю, что она не в формате, в котором она подтверждает объекты по Serializable протоколу.

2. Попробуйте добавить информацию о типе к элементам, например: self.storage.getIems { (elements:[CarouselPoi]) in

3. это не работает .. Cannot convert value of type ([CarouselPoi]) -> ()` к ожидаемому типу аргумента ([_]?) -> Void

Ответ №1:

Этот протокол не выполняет то, что вы думаете, что он делает:

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

Это говорит о том, что сериализуемое может быть сериализовано в словарь, и что любой сериализуемый тип имеет статический метод, который преобразует словарь во что-то (T). У этого «чего-то» нет никаких обещаний. Это не имеет ничего общего с сериализуемым типом. У компилятора нет способа угадать этот тип, кроме как увидев, каким вы просили быть возвращаемое значение.

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

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

Это говорит о том, что вы имеете в виду, но это почти невозможно реализовать таким образом, чтобы не произошел сбой. Что, если словарь не содержит ожидаемых ключей? Что вы тогда возвращаете? Этот метод должен быть либо необязательным, либо вызывающим, и он был бы намного быстрее при инициализации. Например:

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

Благодаря этому большая часть вашей системы будет работать так, как вы ожидаете.

(Все, что сказано, обязательно посмотрите на NSCoding , который уже делает то, что вы пытаетесь сделать, более мощным способом. Есть причины не использовать NSCoding , но убедитесь, что это активный выбор, а не просто изобретать его заново.)


Этот протокол также не говорит о том, что вы, похоже, имеете в виду:

 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 вы неявно передаете тип в качестве одного из параметров. Каждый раз, когда вы вызываете getItems , вы можете передавать другой T (точно так же, как вы можете передавать другой completion . В T нет ничего, что связывало бы его с этим хранилищем. Вот как работают общие файлы. Вам нужен тип, который привязан к хранилищу и согласован со всеми методами в хранилище. Это связанный тип.

Комментарии:

1. Я пробовал ваш подход, но у меня возникают те же проблемы.. Я отредактировал мой вопрос

2. У вас такая же проблема при десериализации и в GetItems. Вы продолжаете вводить параметр T, который вы не даете компилятору никакого способа ограничить. Что вы ожидаете, что T будет в строке, в которой вы получаете ошибку?

3. В StorageCoordinators мне все еще нужны T элементов, именно в классе, где я вызываю StorageCoordinators, мне нужен реальный объект. Извините, но я не очень хорошо разбираюсь в дженериках. Я пытался добавить as в guard let, где я получаю элементы после вызова GetItems, но это не работает