#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, но это не работает