때때로 코어 데이터를 처리하지 않고 캐시 내용을 디스크에 저장하지 않는 것이 더 편리 할 수 있습니다. 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
관련된 유형은 그 Set
형식 매개 변수에 대한 요구 사항이기 때문에 Hashable
수 있습니다.
다음으로 우리는 헬퍼 클래스 CacheCoding
를 사용하여 Cache
에 대한 NSCoding
을 구현해야한다 : 여기
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
을 준수 유형입니다.NSCoder.encode(forKey:)
방법 키 매개 변수를 수용 String
때문에 String
로 전환 될
- 스위프트
CustomStringConvertible
프로토콜 :
C.Key
연관된 유형에 부합한다. 인코딩시 사용 된 NSCoder
키에서 디코딩하는 동안 추출 할 수있는 방법이 없기 때문에
- 스위프트
ExpressibleByStringLiteral
프로토콜은 우리가 keys
키 NSCoder
에 [String]
에 Set<Key>
을 변환하고 저장해야 Set<Key>
- 다시
[String]
를 변환하는 사물. 그러나 캐시에 키가 keys
인 항목이있을 때 캐시 키를 keys
키와 구별하기 위해 캐시 키 앞에 _
을 접두어로 붙이는 경우가 있습니다. 당신이 NSCoding
인스턴스에서 다시 값을 얻을 필요가 있기 때문에 protocol NSCodingConvertible {
associatedtype Coding: NSCoding
var coding: Coding { get }
}
Value.Coding
있다가 ValueProvider
프로토콜을 준수하기 :
C.Value
관련된 유형은 값에서 NSCoding
인스턴스를 캐시에 저장 얻을 NSCodingConvertible
프로토콜을 준수해야한다 :
protocol ValueProvider {
associatedtype Value
var value: Value { get }
}
C.Value.Coding.Value
및 C.Value
은 동일해야 becau 우리가 NSCoding
인스턴스를 얻는 값은 인코딩 할 때 디코딩 할 때 NSCoding
에서 되돌아 오는 값과 동일한 유형이어야합니다.
protocol Builder {
associatedtype Value
init()
func build() -> Value
}
다음의가 NSCache
이 Cache
프로토콜을 준수합시다 :
CB
는 Builder
프로토콜을 준수하고 C
유형의 캐시 인스턴스를 생성하는 데 도움이되는 유형입니다. 여기에 문제가 있습니다. NSCache
은 NSCoder
과 동일한 문제가 있습니다. 저장된 개체의 키를 추출하는 방법을 제공하지 않습니다. 이 문제를 해결하는 방법은 세 가지가 있습니다
키
Set
를 개최
NSCache
대신 사방을 사용합니다
사용자 정의 유형 랩 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
프로토콜을 준수하는 유형이므로 사용 준비가 완료되었습니다.
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)
}
}
일부 가독성을위한 typealiases :
typealias BookCache = BetterCache<StringKey, Book>
typealias BookCacheCoding = CacheCoding<BookCache, BookCacheBuilder>
그리고 빌더 우리가 Cache
를 인스턴스화하는 데 도움이 될 것입니다
는 이제 우리가 그 유형의 캐시와 NSCoding
에 저장할 경우 유형 Book
을 정의 할 수 예 : 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)
}
}
작성한 거의 모든 데이터베이스 앱에서 핵심 데이터를 사용했지만 가장 적합하지 않은 것처럼 보입니다. 캐시를 사용하여 API 결과를 저장하고 실행시 및 이미로드 된 항목을로드 할 때 앱을 지루하게 유지합니다. 핵심 데이터 데이터베이스가 손에 들고 성장하는 것에 대해 걱정하고 있습니다. 핵심 데이터가 "임시"데이터를 캐싱하는 데 가장 잘 사용되는 것처럼 보이지 않습니다. 필자가 필요로하는 지속성은 기본적으로 필자가 필요로하는 메모리 내 캐싱 기능의 2 차 기능이며, 이것이 NSCache를 향한 것입니다. –
예 - 당신의 수수께끼를 볼 수 있습니다. NSCoding은 구현하기가 어렵지 않습니다. 언제나 그 길로 갈 수 있습니다. 그렇다면 캐시의 영구 버전을 작성/업데이트 할시기가됩니다. 내 경험상, 모든 복잡성을 지닌 지속성 휠을 다시 발명하는 길을 향해가는 것은 꽤 쉽습니다. 그리고 물론, 최고의 성능을 발휘하는 응용 프로그램이 먼저 제공됩니다. ;) – bbum
@CoryImdieke, 우리는 지금 같은 상황에 직면 해 있으며 NSCache를 사용할 계획입니다. 그냥 어떤 솔루션을 선택했는지 궁금하십니까? – Koolala