Droste
介绍
Droste 是一个轻量级的可组合缓存库,它利用 RxSwift 的 Observable
作为其 API。
用法示例
使用内置缓存
import Droste
let aDisposeBag = DisposeBag()
let url = URL(string: "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&rvprop=content&rvsection=0&titles=pizza&format=json")!
Caches
.sharedJSONCache
.get(url)
.subscribe(onNext: { jsonObject in
//use jsonObject
}).disposed(by: aDisposeBag)
或自定义缓存
import Droste
let aDisposeBag = DisposeBag()
let url = URL(string: "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&rvprop=content&rvsection=0&titles=pizza&format=json")!
let networkFetcher = NetworkFetcher()
.mapKeys { (url: URL) -> URLRequest in //map url to urlrequest
return URLRequest(url: url)
}
let diskCache = DiskCache<URL, NSData>()
let ramCache = RamCache<URL, NSData>()
let dataCache = ramCache + (diskCache + networkFetcher).reuseInFlight()
let jsonCache = dataCache
.mapValues(f: { (data) -> AnyObject in
//convert NSData to json object
return try JSONSerialization.jsonObject(with: data as Data, options: [.allowFragments]) as AnyObject
}, fInv: { (object) -> NSData in
//convert json object to NSData
return try JSONSerialization.data(withJSONObject: object, options: []) as NSData
})
jsonCache
.get(url)
.subscribe(onNext: { jsonObject in
//use jsonObject
}).disposed(by: aDisposeBag)
特性
- 自带支持 UIImage, JSON, NSData
- 可过期的缓存
- 轻松合并缓存(例如 ram + disk + network)
- 因为
.get
在订阅时返回一个Cancelable
,所以你可以随时取消请求 - 使用
.mapValues
和.mapKeys
通过映射缓存值来合并不同类型的缓存 - 当缓存成功时,通知所有高层缓存设置新值
- 即使值存在,也通过使用
.forwardRequest
操作符将请求转发到底层缓存 - 使用
.switchCache
操作符在运行时创建缓存的条件组合 - 如果请求很昂贵,可以使用
.reuseInFlight
操作符合并具有相同键且第一个请求尚未完成的请求 - 使用
.skipWhile
操作符根据条件在运行时跳过缓存 - 如果你想将值不存在视为错误,请使用显式的 .get
- 通过
Observable
链式序列进行适当的错误处理
开箱即用
我们提供了一些开箱即用的缓存单例,可供使用。
Caches.sharedJSONCache
接受 URL 并返回 AnyObject,根据端点响应可以是 Array 或 DictionaryCaches.sharedDataCache
接受 URL 并返回 NSDataCaches.sharedImageCache
接受 URL 并返回 UIImage
组合缓存
您可以组合不同类型的缓存,例如将 ram 缓存与由网络获取器支持的磁盘缓存组合。要组合两个或多个缓存,可以使用 .compose
或 +
操作符。
示例
let ramCache = RamCache<URL, NSData>()
let diskCache = DiskCache<URL, NSData>()
//First hit ram cache and in case of failure, hit the disk cache
let ramDiskCache = ramCache.compose(other: diskCache)
//Same composition example using the the overloaded `+` operator
let ramDiskCache = ramCache + diskCache
可过期缓存
您可以为每个支持过期的缓存设置过期时间。DiskCache 和 RamCache 都默认支持!您可以按以下方式设置过期时间:
// seconds from now
let ramCache = RamCache<URL, NSData>().expires(at: .seconds(30))
// or by explicitly setting a date
let diskCache = DiskCache<URL, NSData>().expires(at: .date(Date(timeIntervalSince1970: 1516045540)))
在 ram 和 disk 缓存中刷新资源 5 分钟后。
let ramCache = RamCache<URL, NSData>().expires(at: .seconds(600))
let diskCache = DiskCache<URL, NSData>().expires(at: .seconds(600))
let dataCache = ramCache + diskCache + networkFetcher
取消请求
Droste 利用 RxSwift 的功能,为我们提供了一些额外的免费功能,例如取消正在进行的请求。
DisposeBag
的示例
带有 var aDisposeBag = DisposeBag()
let diskCache = DiskCache<URL, NSData>()
let networkFetcher = NetworkFetcher<URL, NSData>()
let diskNetworkCache = diskCache + networkFetcher
diskNetworkCache
.get("a url")
.subscribe(onNext: { response in
}).disposed(by: aDisposeBag)
//...later
aDisposeBag = DisposeBag() // this line will cancel the on-going request
flatMapLatest
的示例
带有 var aDisposeBag = DisposeBag()
let diskCache = DiskCache<URL, NSData>()
let networkFetcher = NetworkFetcher<URL, NSData>()
let diskNetworkCache = diskCache + networkFetcher
//Each time the user input emits a new value, it cancels the previous request in cache
someUserInput
.flatMapLatest{ userInput in
let key = exampleKey(from: userInput)
return diskNetworkCache.get(key)
}
.subscribe(onNext: { response in
}).disposed(by: aDisposeBag)
映射值和键
不同的缓存可能知道不同类型的值和键。例如,您可能有一个使用 String
类型键并返回 UIImage
类型的 RAM 缓存,以及一个使用 URL 作为键并返回 NSData 的网络获取器(它是一种缓存类型)。如果您想合并这两个缓存,您不能直接操作,因为它们的类型不匹配。您可以使用 mapValues
和 mapKeys
来映射缓存中的值和键。
示例
let networkFetcher = NetworkFetcher() //Value = NSData, Key = URLRequest
let ramCache = RamCache<String, UIImage>() //Value = UIImage, Key = String
let imageNetworkFetcher = networkFetcher //Value = UIImage, Key = String
.mapKeys { (urlString: String) -> URLRequest in
let url = URL(string: urlString)!
return URLRequest(url: url)
}
.mapValues(
f: { (data) -> UIImage in
return UIImage(data: data as Data)!
}) { (image) -> NSData in
return UIImagePNGRepresentation(image) as! NSData
}
let imageRamNetworkCache = ramCache + imageNetworkFetcher //Value = UIImage, Key = String
imageRamNetworkCache.get("http://an.image.url.png")
.subscribe(onNext: { image in
//in a view controller context
self.imageView.image = image
}).disposed(by: disposeBag)
更多示例 见此处
发送请求
在你将两个缓存组合在一起,并希望右边的缓存始终从网络获取数据,而不管第一个缓存是否成功的情况下,你可以使用发送请求操作符。通过这种方式,你可以从第一个缓存(如果存在)中获取值,并跟随第二个缓存的值(如果存在)。请注意,.forwardRequest
操作符不提供任何保证,以确保发射的顺序与组合的顺序一致。
示例
假设你有一个屏幕需要立即加载并显示缓存的结果,但会在网络请求成功时始终更新。
let screenCache = diskCache.forwardRequest() + networkFetcher
screenCache
.get("withKey")
.subscribe(onNext: { screenViewModel
self.viewModel = screenViewModel //if disk cache has a value this will be called twice
})
切换缓存
根据使用的键在运行时切换缓存。
示例
let cacheA = ...
let cacheB = ...
let cache = switchCache(cacheA: cacheA, cacheB: cacheB) { (key) -> CacheSwitchResult in
if key == "Hello" {
return .cacheA
}
if key == "World" {
return .cacheB
}
}
cache.get("Hello") //this will use cacheA
cache.get("World") //this will use cacheB
重用执行中的资源
当你想从一个缓存/检索器(例如 NetworkFetcher
)获取一个昂贵的资源时,你可以使用 .reuseInFlight
操作符,当有一个相同键在执行的请求时,合并所有具有相同键的请求。这种场景的一个例子是,一个聊天应用在屏幕上显示同一个头像多次,每次头像都会从缓存中发出自己的请求。使用这个操作符意味着只有一个请求将被发出,任何后续的请求都将共享第一个请求的响应。
示例
let cache = memoryCache + (diskCache + networkFetcher).reuseInFlight()
cache.get("profileImage")
.subscribe(onNext: { image in
avatar1.image = image
})
//a short while after, before the first request finished
cache.get("profileImage")
.subscribe(onNext: { image in
avatar2.image = image //this will get the same response as the above without making a second request
}
备注
- 键必须是可哈希的。
- 此操作符是线程安全的,这意味着您可以从多个队列中分派相同的请求,而它将正常工作且不会出现任何意外的行为。
跳过-while
使用 .skipWhile
操作符可以在运行时根据给定条件跳过缓存。
示例
let updateFromNetwork = true
let conditionedDiskCache = diskCache.skipWhile { key in
let shouldSkip = (key == "a" && updateFromNetwork)
return Observable(shouldSkip)
}
let cache = conditionedDiskCache + networkFetcher
cache.get("a")
.subscribe(onNext: { response in
})
突出获取
我们有两种 .get
操作符,它们的行为略有不同。
func get(_ key: Key) -> Observable<Value?
可选返回值
使用此操作符意味着,如果缓存没有值且内部没有抛出错误,则它将返回一个值 nil
。所有抛出的错误都将如预期传播。
func get(_ key: Key) -> Observable<Value>
非可选返回值
使用此操作符意味着,如果缓存没有值,则将抛出错误 CacheFetchError.valueNotFound
。此操作符 不会改变 其他抛出错误的其他预期行为。
要求
- iOS 9.0+
- Xcode 8.0+
参与其中
- 如果您希望贡献某种内容,请随时提交PRs。
- 如果您有功能请求,请开启一个issue。
- 如果您发现了一个bug或需要帮助,请在提交issue前,先检查旧issue,常见问题解答和StackOverflow(标签'Droste')上的线程。
在贡献之前,请查阅CONTRIBUTING文件以获取更多信息。
如果您在应用中使用强 fuzzy,我们非常想了解您的使用情况!请在Twitter联系我们。
示例
按照以下3个步骤运行示例项目
- 克隆Droste仓库
- 打开Example/Example.xcworkspace工作空间
- 运行Example项目
您也可以通过在Droste项目下的Example/Example.workspace子目录中的Droste Playground进行实验和学习。
安装
CocoaPods
要安装Droste,只需要在Podfile中添加以下行:
pod 'Droste', '~> 0.2.0'
Carthage
要安装Droste,只需要在Cartfile中添加以下行:
github "gtsifrikas/Droste" ~> 0.2.0
版本1.0的任务
- 100%覆盖率
- 支持macOS
- 支持linux
- 更新README以包含自定义Cache创建。
- 提出一个API以支持TTL,并在DiskCache和RamCache中实现它。
作者
更改记录
查看更改记录。