Parfois, il peut être plus pratique de ne pas gérer les données de base et simplement de sauvegarder le contenu du cache sur le disque. Vous pouvez réaliser cela avec NSKeyedArchiver
et UserDefaults
(j'utilise Swift 3.0.2 dans les exemples de code ci-dessous).
D'abord nous allons faire abstraction de NSCache
et imaginer que nous voulons être en mesure de persister tout cache conforme au protocole:
protocol Cache {
associatedtype Key: Hashable
associatedtype Value
var keys: Set<Key> { get }
func set(value: Value, forKey key: Key)
func value(forKey key: Key) -> Value?
func removeValue(forKey key: Key)
}
extension Cache {
subscript(index: Key) -> Value? {
get {
return value(forKey: index)
}
set {
if let v = newValue {
set(value: v, forKey: index)
} else {
removeValue(forKey: index)
}
}
}
}
Key
type associé doit être Hashable
parce que c'est nécessaire pour Set
paramètre de type.
Ensuite, nous devons mettre en œuvre NSCoding
pour Cache
en utilisant la classe d'aide CacheCoding
:
private let keysKey = "keys"
private let keyPrefix = "_"
class CacheCoding<C: Cache, CB: Builder>: NSObject, NSCoding
where
C.Key: CustomStringConvertible & ExpressibleByStringLiteral,
C.Key.StringLiteralType == String,
C.Value: NSCodingConvertible,
C.Value.Coding: ValueProvider,
C.Value.Coding.Value == C.Value,
CB.Value == C {
let cache: C
init(cache: C) {
self.cache = cache
}
required convenience init?(coder decoder: NSCoder) {
if let keys = decoder.decodeObject(forKey: keysKey) as? [String] {
var cache = CB().build()
for key in keys {
if let coding = decoder.decodeObject(forKey: keyPrefix + (key as String)) as? C.Value.Coding {
cache[C.Key(stringLiteral: key)] = coding.value
}
}
self.init(cache: cache)
} else {
return nil
}
}
func encode(with coder: NSCoder) {
for key in cache.keys {
if let value = cache[key] {
coder.encode(value.coding, forKey: keyPrefix + String(describing: key))
}
}
coder.encode(cache.keys.map({ String(describing: $0) }), forKey: keysKey)
}
}
ici:
C
est le type qui est conforme à Cache
.
C.Key
type associé doit être conforme à:
- Swift
CustomStringConvertible
protocole à être converti en String
parce que la méthode NSCoder.encode(forKey:)
accepte String
pour le paramètre clé.
- Swift
ExpressibleByStringLiteral
protocole pour convertir [String]
Retour à Set<Key>
- Nous devons convertir
Set<Key>
-[String]
et stocker à NSCoder
avec keys
clé, car il n'y a pas moyen d'extraire lors du décodage de NSCoder
clés qui ont été utilisées lors de l'encodage objets. Mais il peut y avoir une situation où nous avons également l'entrée dans le cache avec la clé keys
afin de distinguer les clés de cache de clé spéciale keys
nous préfixons les clés de cache avec _
.
C.Value
type associé doit se conformer à NSCodingConvertible
protocole pour obtenir NSCoding
instances des valeurs stockées dans le cache:
protocol NSCodingConvertible {
associatedtype Coding: NSCoding
var coding: Coding { get }
}
Value.Coding
doit se conformer à ValueProvider
protocole car vous avez besoin pour obtenir des valeurs de retour de NSCoding
instances:
protocol ValueProvider {
associatedtype Value
var value: Value { get }
}
C.Value.Coding.Value
et C.Value
doivent être équivalentes becau La valeur à partir de laquelle nous obtenons NSCoding
instance lorsque l'encodage doit avoir le même type que la valeur que nous revenons de NSCoding
lors du décodage.
CB
est un type qui est conforme à Builder
protocole et aide à créer une instance de cache de type C
:
protocol Builder {
associatedtype Value
init()
func build() -> Value
}
Ensuite, nous allons faire NSCache
sont conformes à Cache
protocole. Ici nous avons un problème. NSCache
a le même problème que NSCoder
- il ne fournit pas le moyen d'extraire les clés pour les objets stockés. Il y a trois façons de contourner cela:
Wrap NSCache
avec le type personnalisé qui conserve les clés Set
et l'utiliser partout au lieu de NSCache
:
class BetterCache<K: AnyObject & Hashable, V: AnyObject>: Cache {
private let nsCache = NSCache<K, V>()
private(set) var keys = Set<K>()
func set(value: V, forKey key: K) {
keys.insert(key)
nsCache.setObject(value, forKey: key)
}
func value(forKey key: K) -> V? {
let value = nsCache.object(forKey: key)
if value == nil {
keys.remove(key)
}
return value
}
func removeValue(forKey key: K) {
return nsCache.removeObject(forKey: key)
}
}
Si vous avez encore besoin de passer NSCache
quelque part alors vous pouvez essayer de l'étendre en Objective-C en faisant la même chose que ci-dessus avec BetterCache
.
Utilisez une autre implémentation de cache.
Maintenant vous avez le type qui est conforme au protocole Cache
et vous êtes prêt à l'utiliser.
Définissons le type Book
qui cas, nous allons stocker dans le cache et NSCoding
pour ce type:
class Book {
let title: String
init(title: String) {
self.title = title
}
}
class BookCoding: NSObject, NSCoding, ValueProvider {
let value: Book
required init(value: Book) {
self.value = value
}
required convenience init?(coder decoder: NSCoder) {
guard let title = decoder.decodeObject(forKey: "title") as? String else {
return nil
}
print("My Favorite Book")
self.init(value: Book(title: title))
}
func encode(with coder: NSCoder) {
coder.encode(value.title, forKey: "title")
}
}
extension Book: NSCodingConvertible {
var coding: BookCoding {
return BookCoding(value: self)
}
}
Quelques typealiases pour une meilleure lisibilité:
typealias BookCache = BetterCache<StringKey, Book>
typealias BookCacheCoding = CacheCoding<BookCache, BookCacheBuilder>
et le constructeur qui nous aidera à instancier Cache
instance:
class BookCacheBuilder: Builder {
required init() {
}
func build() -> BookCache {
return BookCache()
}
}
Testez-le:
let cacheKey = "Cache"
let bookKey: StringKey = "My Favorite Book"
func test() {
var cache = BookCache()
cache[bookKey] = Book(title: "Lord of the Rings")
let userDefaults = UserDefaults()
let data = NSKeyedArchiver.archivedData(withRootObject: BookCacheCoding(cache: cache))
userDefaults.set(data, forKey: cacheKey)
userDefaults.synchronize()
if let data = userDefaults.data(forKey: cacheKey),
let cache = (NSKeyedUnarchiver.unarchiveObject(with: data) as? BookCacheCoding)?.cache,
let book = cache.value(forKey: bookKey) {
print(book.title)
}
}
J'ai utilisé des données de base sur presque toutes les applications de base de données que j'ai créées, mais il semble que ce ne soit pas la meilleure solution. J'utilise les caches pour stocker les résultats de l'API, en gardant l'application zippy au lancement et en chargeant les choses qui ont déjà été chargées. Je m'inquiète de voir la base de données de base devenir incontrôlable et grandissante. Il ne semble pas que les données de base soient mieux utilisées pour mettre en cache des données "temporaires". La persistance dont j'ai besoin est fondamentalement une fonction secondaire à la fonctionnalité de mise en cache en mémoire dont j'ai besoin, c'est pourquoi je me suis orienté vers NSCache. –
Yah - Je peux voir votre énigme. NSCoding n'est pas * difficile * à mettre en œuvre - vous pouvez toujours suivre cette voie. Ensuite, la question devient quand écrire/mettre à jour la version persistante du cache. D'après mon expérience, il est assez facile de suivre une voie qui finit par réinventer la roue de la persistance avec toutes ses complexités. Et, bien sûr, l'application la plus performante est celle qui expédie en premier. ;) – bbum
@CoryImdieke, nous sommes confrontés à la même situation maintenant et je prévois d'utiliser NSCache. Je me demandais quelle solution avez-vous choisie? – Koolala