Clean up DownloadWithCacheManager’s cache at most every five minutes — it’s very expensive to do it on every request. This dramatically helps sidebar scrolling performance when you have a couple thousand feeds all trying to get their favicons.

This commit is contained in:
Brent Simmons 2017-12-13 21:18:20 -08:00
parent e6dee88e2e
commit b04876185d

View File

@ -65,11 +65,6 @@ private struct WebCacheRecord {
let dateDownloaded: Date let dateDownloaded: Date
let data: Data let data: Data
let response: URLResponse let response: URLResponse
func isExpired(_ timeToLive: TimeInterval) -> Bool {
return Date().timeIntervalSince(dateDownloaded) > timeToLive
}
} }
private final class WebCache { private final class WebCache {
@ -77,18 +72,19 @@ private final class WebCache {
private var cache = [URL: WebCacheRecord]() private var cache = [URL: WebCacheRecord]()
func cleanup(_ cleanupInterval: TimeInterval) { func cleanup(_ cleanupInterval: TimeInterval) {
let cutoffDate = Date(timeInterval: -cleanupInterval, since: Date())
cache.keys.forEach { (key) in cache.keys.forEach { (key) in
let cacheRecord = self[key]! let cacheRecord = self[key]!
if shouldDelete(cacheRecord, cleanupInterval) { if shouldDelete(cacheRecord, cutoffDate) {
cache[key] = nil cache[key] = nil
} }
} }
} }
private func shouldDelete(_ cacheRecord: WebCacheRecord, _ cleanupInterval: TimeInterval) -> Bool { private func shouldDelete(_ cacheRecord: WebCacheRecord, _ cutoffDate: Date) -> Bool {
return Date().timeIntervalSince(cacheRecord.dateDownloaded) > cleanupInterval return cacheRecord.dateDownloaded < cutoffDate
} }
subscript(_ url: URL) -> WebCacheRecord? { subscript(_ url: URL) -> WebCacheRecord? {
@ -115,26 +111,24 @@ private final class DownloadWithCacheManager {
static let shared = DownloadWithCacheManager() static let shared = DownloadWithCacheManager()
private var cache = WebCache() private var cache = WebCache()
private static let timeToLive: TimeInterval = 10 * 60 // 10 minutes private static let timeToLive: TimeInterval = 10 * 60 // 10 minutes
private static let cleanupInterval: TimeInterval = 30 * 60 // 30 minutes private static let cleanupInterval: TimeInterval = 5 * 60 // clean up the cache at most every 5 minutes
private var lastCleanupDate = Date()
func download(_ url: URL, _ callback: @escaping OneShotDownloadCallback) { func download(_ url: URL, _ callback: @escaping OneShotDownloadCallback) {
cache.cleanup(DownloadWithCacheManager.cleanupInterval) if lastCleanupDate.timeIntervalSinceNow < -(5 * 60) {
lastCleanupDate = Date()
cache.cleanup(DownloadWithCacheManager.timeToLive)
}
let cacheRecord: WebCacheRecord? = cache[url] let cacheRecord: WebCacheRecord? = cache[url]
if let cacheRecord = cacheRecord, !cacheRecord.isExpired(DownloadWithCacheManager.timeToLive) { if let cacheRecord = cacheRecord {
callback(cacheRecord.data, cacheRecord.response, nil) callback(cacheRecord.data, cacheRecord.response, nil)
return return
} }
OneShotDownloadManager.shared.download(url) { (data, response, error) in OneShotDownloadManager.shared.download(url) { (data, response, error) in
if let _ = error, let cacheRecord = cacheRecord {
// In the case where a cache record has expired, but the download returned an error, we use the cache record anyway. By design.
callback(cacheRecord.data, cacheRecord.response, nil)
return
}
if let data = data, let response = response, response.statusIsOK, error == nil { if let data = data, let response = response, response.statusIsOK, error == nil {
let cacheRecord = WebCacheRecord(url: url, dateDownloaded: Date(), data: data, response: response) let cacheRecord = WebCacheRecord(url: url, dateDownloaded: Date(), data: data, response: response)
self.cache[url] = cacheRecord self.cache[url] = cacheRecord