受http://khanlou.com/2017/10/the-flat-cache/启发的Swift实现的扁平缓存
Cache
作为单一真相来源Cached
值随时间的变化Related
值将以下行添加到您的Podfile
pod 'Pancake'
将以下行添加到您的Cartfile
github "zradke/Pancake"
Identifiable
模型要插入到Cache
中,类型只需是Identifiable
struct Book: Identifiable {
typealias ISBN = Int
var identifier: ISBN
var title: String
}
任何类型都可以作为Identifier
使用,只要它符合CustomStringConvertible
、Hashable
和Codable
。由于Cache
更适合使用值类型而不是引用类型,因此对于模型和Identifier
都应首选使用struct
而不是class
。
Cache
中添加值任何Identifiable
值都可以插入到Cache
中
let cache = Cache()
let book = Book(identifier: 9788700631625,
title: "Harry Potter and the Sorcerer's Stone")
cache.set(book)
一旦在Cache
中,就可以使用类型标识符来检索值
let retreivedBook: Book? = cache.get(9788700631625)
Cached
值Cache
中的值也可以包裹成Cached
值
let cachedBook: Cached<Book> = cache.cached(9788700631625)
Cached
值从Cache
提供最新的值,同时也可以被观察
// Retreives the latest value from the `Cache`
let currentValue = cachedBook.value
// Executes the closure whenever the value is changed in the `Cache`
let disposable = cachedBook.observe { (book) in
// update user interface etc.
}
请注意,CachedValue.observe(_:)
返回一个Cache.Disposable
,必须保留以保持观察的生命周期。一旦它被释放,观察就会自动结束。
Mergeable
模型API通常会返回相同模型的未完整表示。如果类型是Mergeable
,则Cache
可以逐渐建立完整的模型
struct Book: Identifiable, Mergeable {
typealias ISBN = Int
var identifier: ISBN
var title: String?
var publishedOn: Date?
func merged(with other: Book) -> Book {
var copy = self
if let title = other.title { copy.title = title }
if let publishedOn = other.publishedOn { copy.publishedOn = publishedOn }
return copy
}
}
当一个Mergeable
值被插入到缓存中时,它会与任何现有值合并
let bookStub = Book(identifier: 9788700631625,
title: "Harry Potter and the Sorcerer's Stone",
publishedOn: nil)
cache.set(bookStub)
// Later from a different API...
let detailedBook = Book(identifier = 9788700631625,
title: nil,
publishedOn: "1998-09-01".toDate())
cache.set(detailedBook)
let compositeBook: Book = cache.get(9788700631625)!
compositeBook.title // "Harry Potter and the Sorcerer's Stone"
compositeBook.publishedOn // 1998-09-01
创建大量模型的Mergeable
实现可能是苦差事,在这种情况下可以使用Sourcery。
HasCachedRelationships
的模型模型通常具有关系。通过存储所有值的单一表示并允许通用关系,Cache
可以帮助归一化数据。类型通过符合HasCachedRelationships
来指示它具有关系,这通常由任何Related
属性构建
struct Author: Identifiable, HasCachedRelationships {
...
var books: Set<Related<Book>>
var relatedCacheKeys: Set<CacheKey> {
return books.map { $0.cacheKey }
}
}
struct Book: Identifiable, HasCachedRelationships {
...
var author: Related<Author>
var relatedCacheKeys: Set<CacheKey> {
return [author.cacheKey]
}
}
Related
值可以通过使用Related.cached(in:)
来转换为Cached
值以访问其实际值。然而,HasCachedRelationships
的更大好处是,当缓存中相关对象变化时,观察者会被通知,这使得依赖于嵌套值的UI始终保持在同步状态
let cache = Cache()
var author = Author(identifier: 1,
name: "JK Rowling")
let book = Book(identifier: 9788700631625,
title: "Harry Potter and the Sorcerer's Stone",
author: Related(author))
author.books.append(Related(book))
cache.set(author)
cache.set(book)
let disposable = cache.cached(book).observe { (value) in
// Update UI
}
author.bornOn = "1965-07-31".toDate()
cache.set(author) // Notifies the UI
Cache
非常智能,能够处理循环关系和任意深度的关系(尽管太深可能会导致性能问题)。
类似于Mergeable
,创建HasCachedRelationships.relatedCacheKeys
通常涉及样板代码,所以我建议使用Sourcery来自动化大量模型的过程。
API通常会返回除主要模型外的相关模型,所有这些模型都在处理期间需要插入Cache
。然而,作为对Cache
的单独调用可能会产生性能影响,因为Cache
需要做一些工作来确保线程安全,更不用说所有将被生成的观察结果。
因此,对Cache
执行批处理操作是有用的。
cache.performBatchUpdates { (cache) in
cache.set(...)
cache.set(...)
}
在批量更新期间,闭包被赋予一个CacheType
,这是Cache
的一个精简版本,可以用来获取和设置缓存中的值。给定的CacheType
在多个线程中使用是不安全的,但这也使它使用起来更快。观察结果在闭包之后合并并执行。
Zach Radke,[email protected]
Pancake在MIT许可下可用。更多信息请参阅LICENSE文件。