Add CachingDiskStorage

This commit is contained in:
Shadowfacts 2021-01-18 13:52:15 -05:00
parent de67327f6d
commit 0b008489f7
4 changed files with 77 additions and 6 deletions

View File

@ -113,6 +113,7 @@
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2425217ABF63005076CC /* UserActivityType.swift */; };
D62FF04823D7CDD700909D6E /* AttributedStringHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */; };
D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C4F25B3765B00B27539 /* ImageDataCache.swift */; };
D6311C5625B4CEA000B27539 /* CachingDiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C5525B4CEA000B27539 /* CachingDiskStorage.swift */; };
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Helpers.swift */; };
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
D63569E023908A8D003DD353 /* StatusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60A4FFB238B726A008AC647 /* StatusState.swift */; };
@ -470,6 +471,7 @@
D62D2425217ABF63005076CC /* UserActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityType.swift; sourceTree = "<group>"; };
D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringHelperTests.swift; sourceTree = "<group>"; };
D6311C4F25B3765B00B27539 /* ImageDataCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDataCache.swift; sourceTree = "<group>"; };
D6311C5525B4CEA000B27539 /* CachingDiskStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachingDiskStorage.swift; sourceTree = "<group>"; };
D6333B362137838300CE884A /* AttributedString+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Helpers.swift"; sourceTree = "<group>"; };
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; };
D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesNavigationController.swift; sourceTree = "<group>"; };
@ -1496,6 +1498,7 @@
D6F1F84C2193B56E00F5FE67 /* Cache.swift */,
04DACE8D212CC7CC009840C4 /* ImageCache.swift */,
D6311C4F25B3765B00B27539 /* ImageDataCache.swift */,
D6311C5525B4CEA000B27539 /* CachingDiskStorage.swift */,
);
path = Caching;
sourceTree = "<group>";
@ -1874,6 +1877,7 @@
D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */,
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */,
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */,
D6311C5625B4CEA000B27539 /* CachingDiskStorage.swift in Sources */,
D62275AA24F1E01C00B82A16 /* ComposeTextView.swift in Sources */,
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */,
D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */,

View File

@ -0,0 +1,71 @@
//
// CachingDiskStorage.swift
// Tusker
//
// Created by Shadowfacts on 1/17/21.
// Copyright © 2021 Shadowfacts. All rights reserved.
//
import Foundation
import Cache
/// This class wraps a `DiskStorage` and maintains an in-memory cache of whether objects
/// exist on disk to avoid unnecessary disk I/O when calling synchronous methods like
/// `existsObject(forKey:)`.
class CachingDiskStorage<T>: StorageAware {
private let storage: DiskStorage<T>
private var files = [String: FileState]()
init(config: DiskConfig, transformer: Transformer<T>) throws {
storage = try DiskStorage(config: config, transformer: transformer)
}
private func state(for key: String) -> FileState {
return files[key] ?? .unknown
}
func entry(forKey key: String) throws -> Entry<T> {
if state(for: key) == .doesNotExist {
throw StorageError.notFound
}
return try storage.entry(forKey: key)
}
func existsObject(forKey key: String) throws -> Bool {
switch state(for: key) {
case .unknown:
return try storage.existsObject(forKey: key)
case .exists:
return true
case .doesNotExist:
return false
}
}
func removeObject(forKey key: String) throws {
try storage.removeObject(forKey: key)
files[key] = .doesNotExist
}
func setObject(_ object: T, forKey key: String, expiry: Expiry? = nil) throws {
try storage.setObject(object, forKey: key, expiry: expiry)
files[key] = .exists
}
func removeAll() throws {
try storage.removeAll()
files.removeAll()
}
func removeExpiredObjects() throws {
try storage.removeExpiredObjects()
}
}
extension CachingDiskStorage {
private enum FileState {
case unknown, exists, doesNotExist
}
}

View File

@ -37,10 +37,6 @@ class ImageCache {
func get(_ url: URL, completion: ((Data?, UIImage?) -> Void)?) -> Request? {
let key = url.absoluteString
if !ImageCache.disableCaching,
// todo: calling object(forKey: key) does disk I/O and this method is often called from the main thread
// in performance sensitive paths. a nice optimization to DiskStorage would be adding an internal cache
// of the state (unknown/exists/does not exist) of whether or not objects exist on disk so that the slow, disk I/O
// path can be avoided most of the time
let entry = try? cache.get(key) {
if let completion = completion {
backgroundQueue.async {

View File

@ -12,7 +12,7 @@ import Cache
class ImageDataCache {
private let memory: MemoryStorage<Entry>
private let disk: DiskStorage<Data>?
private let disk: CachingDiskStorage<Data>?
private let storeOriginalDataInMemory: Bool
private let desiredPixelSize: CGSize?
@ -23,7 +23,7 @@ class ImageDataCache {
if let diskExpiry = diskExpiry {
let diskConfig = DiskConfig(name: name, expiry: diskExpiry)
self.disk = try! DiskStorage(config: diskConfig, transformer: TransformerFactory.forData())
self.disk = try! CachingDiskStorage(config: diskConfig, transformer: TransformerFactory.forData())
} else {
self.disk = nil
}