Не совсем понятно, для чего вы планируете использовать этот репозиторий.Репозиторий предназначен для сред, которые имеют определенные характеристики (например, сильные связи с базами данных в стиле строк).Я расскажу о других шаблонах, которые часто работают лучше в обычных приложениях для iOS (и даже в небольших приложениях Mac).
Мне обычно сильно не нравится стирание типов, и это часто указывает на проблему с дизайном.Но в этом случае я думаю, что стирание типа может быть разумным ответом.
Поэтому мы начнем с видов предметов, которые мы можем хранить.Возможно, им понадобится какой-то идентификатор, и он будет хешируемым для многих распространенных бэкэндов (но, может быть, вам не понадобится хеширование; если нет, то удалите его).
protocol Identified {
associatedtype ID
var id: ID { get }
}
typealias Storable = Identified & Hashable
А потомвещи, которые могут выступать в качестве хранилища.Не существует такого понятия, как «RepositoryStorage».Это просто говорит: «Если вы соответствуете этим требованиям, то Repository может использовать вас».
protocol RepositoryStorage {
associatedtype Item: Storable
func get(identifier: Item.ID) -> Item?
func add(item: Item)
func delete(identifier: Item.ID)
func allItems() -> [Item]
}
И затем стандартный, несколько утомительный шаблон для стирания типов (есть еще один шаблон, который использует stdlib, и даже болееутомительно, но этот достаточно хорош для большинства случаев).
// I'm making it a class because I assume it'll have reference semantics.
final class Respository<Item: Storable>: RepositoryStorage {
init<Storage: RepositoryStorage>(storage: Storage) where Storage.Item == Item {
self._get = storage.get
self._add = storage.add
self._delete = storage.delete
self._allItems = storage.allItems
}
let _get: (Item.ID) -> Item?
func get(identifier: Item.ID) -> Item? { return _get(identifier) }
let _add: (Item) -> Void
func add(item: Item) { _add(item) }
let _delete: (Item.ID) -> Void
func delete(identifier: Item.ID) { _delete(identifier) }
let _allItems: () -> [Item]
func allItems() -> [Item] { return _allItems() }
}
Так что все в порядке, это универсальный репозиторий.И это имеет смысл, если вы имеете дело с большим набором элементов, которые, вероятно, будут храниться в базе данных SQLite.Но по моему опыту это часто и слишком много и слишком мало.Слишком много, если это всего лишь несколько предметов, и слишком мало, если у вас много предметов и поэтому, вероятно, придется делать гораздо больше, чем просто CRUD.Возможно, вам нужны Query and Join, а этого недостаточно.(Создание чего-то гибкого в одном направлении часто обрезает вас в других направлениях. Универсального «универсального» не существует).
Так можем ли мы упростить ситуацию для случая, когда на самом деле это всего лишь несколько предметов?Вот подход, которым я регулярно пользуюсь:
class DataStore<Key: Hashable & Codable, Value: Codable> {
let identifier: String
private(set) var storage: DataStorage
var dictionary: [Key: Value] {
didSet {
storage[identifier] = try? PropertyListEncoder().encode(dictionary)
}
}
init(identifier: String, storage: DataStorage = UserDefaults.standard) {
self.identifier = identifier
self.storage = storage
let data = storage[identifier] ?? Data()
self.dictionary = (try? PropertyListDecoder().decode([Key: Value].self,
from: data)) ?? [:]
}
subscript(key: Key) -> Value? {
get { return dictionary[key] }
set { dictionary[key] = newValue }
}
}
DataStore действует как словарь, в котором можно хранить пары ключ / значение:
let ds = DataStore<String: Item>(identifier: "Item")
ds["first"] = item
Он может хранить все, что можно кодировать.С небольшими изменениями вы можете переключить его из словарного интерфейса в массив или в интерфейс, подобный множеству;Я просто обычно хочу словарь.
Когда он обновляется, он кодирует все хранилище данных в свое хранилище как Данные:
protocol DataStorage {
subscript(identifier: String) -> Data? { get set }
}
Это очень быстро и эффективно для десятков элементов.Я мог бы переосмыслить, если бы было более ста предметов, и это было бы неуместно для сотен или более предметов.Но для небольших наборов это очень, очень быстро.
Очень распространенным DataStorage является UserDefaults:
extension UserDefaults: DataStorage {
subscript(identifier: String) -> Data? {
get { return data(forKey: identifier) }
set { set(newValue, forKey: identifier) }
}
}
Ключевым уроком является то, что это избавляет от всех обручей стирания типов.прыжок, создавая общую валюту (данные), с которой работают только нижние уровниКаждый раз, когда вы можете сделать это, отделив универсальный интерфейс верхнего уровня от неуниверсального интерфейса нижнего уровня, вы сэкономите много времени.
Это может или не может работать в вашей ситуации.Он предназначен для хранилищ ключей / значений, а не баз данных, и предназначен для чтения гораздо чаще, чем для записи.Но для такого использования это намного проще и, как правило, быстрее, чем шаблон Repository.Это тот тип компромисса, который я имею в виду, когда говорю, что важно знать ваш вариант использования.