Core Dataを処理せず、キャッシュコンテンツをディスクに保存する方が便利な場合があります。NSKeyedArchiver
これはとで実現できますUserDefaults
(以下のコード例ではSwift 3.0.2を使用しています)。
NSCache
まず、プロトコルに準拠するキャッシュを永続化できるようにすることを考えてみましょう。
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
関連付けられたタイプは、タイプパラメータHashable
の要件である必要があります。Set
次に、ヘルパークラスを使用するNSCoding
ために実装する必要があります。Cache
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)
}
}
ここ:
C
に準拠したタイプですCache
。
C.Key
関連するタイプは、以下に準拠する必要があります。
- メソッドがキーパラメータを受け入れるため、変換可能なSwift
CustomStringConvertible
プロトコル。String
NSCoder.encode(forKey:)
String
- に戻すためのSwift
ExpressibleByStringLiteral
プロトコル[String]
Set<Key>
- オブジェクトのエンコード時に使用されたキーからデコード中に抽出する方法がないため、キーを使用して変換
Set<Key>
し[String]
て保存する必要があります。ただし、キャッシュにキー付きのエントリがある場合もあるため、キャッシュキーと特殊キーを区別するために、キャッシュキーの前に。を付けます。NSCoder
keys
NSCoder
keys
keys
_
C.Value
関連するタイプは、キャッシュに格納されている値からインスタンスNSCodingConvertible
を取得するためにプロトコルに準拠する必要があります。NSCoding
protocol NSCodingConvertible {
associatedtype Coding: NSCoding
var coding: Coding { get }
}
Value.Coding
インスタンスValueProvider
から値を取得する必要があるため、プロトコルに準拠する必要があります。NSCoding
protocol ValueProvider {
associatedtype Value
var value: Value { get }
}
C.Value.Coding.Value
エンコード時にインスタンスを取得する値は、デコード時に取得する値と同じタイプである必要がC.Value
あるため、同等である必要があります。NSCoding
NSCoding
CB
プロトコルに準拠し、次のタイプBuilder
のキャッシュインスタンスを作成するのに役立つC
タイプです。
protocol Builder {
associatedtype Value
init()
func build() -> Value
}
次に、プロトコルにNSCache
準拠させましょう。Cache
ここに問題があります。NSCache
と同じ問題がNSCoder
あります-保存されたオブジェクトのキーを抽出する方法を提供しません。これを回避するには、次の3つの方法があります。
NSCache
キーを保持し、次Set
の代わりにどこでも使用できるカスタムタイプでラップし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)
}
}
それでもどこかを通過する必要がある場合はNSCache
、Objective-Cでそれを拡張して、上記ので行ったのと同じことを行うことができますBetterCache
。
他のキャッシュ実装を使用します。
これで、プロトコルに準拠したタイプがCache
作成され、使用する準備が整いました。
Book
キャッシュに保存するインスタンスのタイプとNSCoding
、そのタイプを定義しましょう。
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)
}
}
読みやすさを向上させるためのいくつかのタイプエイリアス:
typealias BookCache = BetterCache<StringKey, Book>
typealias BookCacheCoding = CacheCoding<BookCache, BookCacheBuilder>
そして、インスタンスをインスタンス化するのに役立つビルダーCache
:
class BookCacheBuilder: Builder {
required init() {
}
func build() -> BookCache {
return BookCache()
}
}
試して:
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)
}
}