Đôi khi có thể thuận tiện hơn là không xử lý Dữ liệu cốt lõi và chỉ để lưu nội dung bộ nhớ cache vào đĩa. Bạn có thể đạt được điều này với NSKeyedArchiver
và UserDefaults
(Tôi đang sử dụng Swift 3.0.2 trong các ví dụ mã bên dưới).
trước tiên hãy trừu tượng từ NSCache
và tưởng tượng rằng chúng tôi muốn để có thể tồn tại bất kỳ bộ nhớ cache mà phù hợp với giao thức:
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
liên quan đến loại có được Hashable
bởi vì đó là yêu cầu đối với Set
tham số kiểu.
Tiếp theo chúng ta phải thực hiện NSCoding
cho Cache
sử dụng lớp helper 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)
}
}
đây:
C
là kiểu đó phù hợp với Cache
.
C.Key
liên quan đến loại có để phù hợp với:
- Swift
CustomStringConvertible
giao thức được chuyển đổi thành String
vì NSCoder.encode(forKey:)
phương pháp chấp nhận String
cho thông số quan trọng.
- Swift
ExpressibleByStringLiteral
giao thức để chuyển đổi [String]
trở lại Set<Key>
- Chúng tôi cần phải chuyển đổi
Set<Key>
-[String]
và lưu nó để NSCoder
với keys
chính vì không có cách nào để trích xuất trong giải mã từ NSCoder
phím đã được sử dụng khi mã hóa các đối tượng. Nhưng có thể có tình huống khi chúng tôi cũng có mục nhập trong bộ nhớ cache với khóa keys
để phân biệt các khóa bộ nhớ cache từ khóa đặc biệt keys
, chúng tôi tiền tố khóa bộ nhớ cache với _
.
C.Value
liên quan đến loại có để phù hợp với NSCodingConvertible
giao thức để có được NSCoding
trường từ các giá trị được lưu trữ trong bộ nhớ cache:
protocol NSCodingConvertible {
associatedtype Coding: NSCoding
var coding: Coding { get }
}
Value.Coding
phải tuân theo các ValueProvider
giao thức bởi vì bạn cần để có được giá trị trở lại từ NSCoding
trường hợp:
protocol ValueProvider {
associatedtype Value
var value: Value { get }
}
C.Value.Coding.Value
và C.Value
phải tương đương becau se giá trị mà từ đó chúng tôi nhận được ví dụ NSCoding
khi mã hóa phải có cùng loại với giá trị mà chúng tôi nhận được từ NSCoding
khi giải mã.
CB
là một loại mà phù hợp với Builder
giao thức và giúp tạo ra ví dụ bộ nhớ cache của C
loại:
protocol Builder {
associatedtype Value
init()
func build() -> Value
}
Tiếp theo chúng ta hãy làm NSCache
phù hợp với Cache
giao thức. Ở đây chúng tôi có một vấn đề. NSCache
có cùng vấn đề với tên NSCoder
- nó không cung cấp cách trích xuất khóa cho các đối tượng được lưu trữ. Có ba cách để workaround này:
Bọc NSCache
với kiểu tùy chỉnh mà sẽ giữ phím Set
và sử dụng nó ở khắp mọi nơi thay vì 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)
}
}
Nếu bạn vẫn cần phải vượt qua NSCache
ở đâu đó rồi bạn có thể thử mở rộng nó trong Objective-C làm điều tương tự như tôi đã làm ở trên với BetterCache
.
Sử dụng một số triển khai bộ nhớ cache khác.
Bây giờ bạn có loại phù hợp với giao thức Cache
và bạn đã sẵn sàng để sử dụng.
Hãy xác định loại Book
mà trường hợp chúng tôi sẽ lưu trữ trong bộ nhớ cache và NSCoding
cho loại đó:
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)
}
}
Một số typealiases để có thể đọc tốt hơn:
typealias BookCache = BetterCache<StringKey, Book>
typealias BookCacheCoding = CacheCoding<BookCache, BookCacheBuilder>
Và builder rằng sẽ giúp chúng ta nhanh chóng Cache
ví dụ:
class BookCacheBuilder: Builder {
required init() {
}
func build() -> BookCache {
return BookCache()
}
}
Kiểm tra:
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)
}
}
Tôi đã sử dụng Dữ liệu cốt lõi trên hầu hết mọi ứng dụng cơ sở dữ liệu tôi đã tạo, nhưng có vẻ như đây không phải là kết quả phù hợp nhất cho nó. Tôi đang sử dụng bộ nhớ cache để lưu trữ kết quả API, giữ cho ứng dụng bị zippy khi khởi chạy và khi tải những thứ đã được tải. Tôi lo lắng về cơ sở dữ liệu cơ sở dữ liệu cốt lõi và không thể phát triển. Nó chỉ có vẻ không phải là dữ liệu cốt lõi được sử dụng tốt nhất cho bộ nhớ đệm "tạm thời" dữ liệu. Sự kiên trì mà tôi cần về cơ bản là chức năng thứ cấp cho chức năng bộ nhớ đệm trong bộ nhớ mà tôi cần, đó là lý do tại sao tôi đang hướng tới NSCache. –
Yah - Tôi có thể thấy câu hỏi hóc búa của bạn. NSCoding không phải là * mà * khó thực hiện - bạn luôn có thể đi xuống con đường đó. Sau đó, câu hỏi sẽ trở thành khi nào để viết/cập nhật phiên bản cache liên tục. Theo kinh nghiệm của tôi, nó là khá dễ dàng để đi xuống một con đường mà kết thúc lên tái phát minh ra bánh xe kiên trì với tất cả các phức tạp của nó. Và, tất nhiên, các ứng dụng thực hiện tốt nhất là một trong những tàu đầu tiên. ;) – bbum
@CoryImdieke, chúng tôi đang đối mặt với tình huống tương tự hiện tại và tôi đang lên kế hoạch sử dụng NSCache. Chỉ cần tự hỏi bạn đã chọn giải pháp nào? – Koolala