Tusker/Tusker/Caching/ImageDataCache.swift

110 lines
3.4 KiB
Swift

//
// ImageDataCache.swift
// Tusker
//
// Created by Shadowfacts on 1/16/21.
// Copyright © 2021 Shadowfacts. All rights reserved.
//
import UIKit
class ImageDataCache {
private let memory: MemoryCache<Entry>
let disk: DiskCache<Data>?
private let storeOriginalDataInMemory: Bool
private let desiredPixelSize: CGSize?
init(name: String, memoryExpiry: CacheExpiry, diskExpiry: CacheExpiry?, storeOriginalDataInMemory: Bool, desiredPixelSize: CGSize?) {
self.memory = MemoryCache(defaultExpiry: memoryExpiry)
if let diskExpiry = diskExpiry {
self.disk = try! DiskCache(name: name, defaultExpiry: diskExpiry)
} else {
self.disk = nil
}
self.storeOriginalDataInMemory = storeOriginalDataInMemory
self.desiredPixelSize = desiredPixelSize
}
func has(_ key: String) throws -> Bool {
if memory.existsObject(forKey: key) {
return true
} else if let disk = self.disk,
try disk.existsObject(forKey: key) {
return true
} else {
return false
}
}
func get(_ key: String, loadOriginal: Bool) throws -> Entry? {
if storeOriginalDataInMemory || !loadOriginal,
let memoryEntry = try? memory.object(forKey: key) {
return memoryEntry
} else if let disk = self.disk,
let data = try? disk.object(forKey: key),
let image = UIImage(data: data) {
return Entry(data: data, image: image)
} else {
return nil
}
}
func getImage(_ key: String) throws -> UIImage? {
return try get(key, loadOriginal: false)?.image
}
func getData(_ key: String) throws -> Data? {
return try get(key, loadOriginal: false)?.data
}
func set(_ key: String, data: Data, image: UIImage?) throws {
guard let image = scaleImageIfDesired(data: data) ?? image else { return }
let entry = Entry(data: storeOriginalDataInMemory ? data : nil, image: image)
memory.setObject(entry, forKey: key)
if let disk = self.disk {
try disk.setObject(data, forKey: key)
}
}
func removeAll() throws {
memory.removeAll()
try? disk?.removeAll()
}
// TODO: consider removing this and letting ImageCache just use the UIImage thumbnailing API
private func scaleImageIfDesired(data: Data) -> UIImage? {
guard let desiredPixelSize = desiredPixelSize,
let source = CGImageSourceCreateWithData(data as CFData, [kCGImageSourceShouldCache: false] as CFDictionary) else {
return nil
}
let maxDimension = max(desiredPixelSize.width, desiredPixelSize.height)
let downsampleOptions: [CFString: Any] = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimension
]
if let downsampled = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions as CFDictionary) {
return UIImage(cgImage: downsampled)
} else {
return nil
}
}
}
extension ImageDataCache {
struct Entry {
let data: Data?
let image: UIImage
}
}