forked from shadowfacts/Tusker
110 lines
3.4 KiB
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
|
|
}
|
|
}
|