You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

274 lines
9.7 KiB

import UIKit
import Foundation
import Combine
// MARK: -
class ImageCacheManager: ObservableObject {
static let shared = ImageCacheManager()
private let cache = NSCache<NSString, UIImage>()
private let fileManager = FileManager.default
private let documentsPath: String
init() {
//
cache.countLimit = 50 //
cache.totalCostLimit = 50 * 1024 * 1024 // 50MB
//
documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
//
NotificationCenter.default.addObserver(
self,
selector: #selector(handleMemoryWarning),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil
)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: -
///
func setImage(_ image: UIImage, forKey key: String) {
cache.setObject(image, forKey: key as NSString)
}
///
func getImage(forKey key: String) -> UIImage? {
return cache.object(forKey: key as NSString)
}
///
func removeImage(forKey key: String) {
cache.removeObject(forKey: key as NSString)
}
///
func clearCache() {
cache.removeAllObjects()
print("🧹 图片内存缓存已清空")
}
// MARK: -
///
func saveImageToFile(_ image: UIImage, withName name: String) -> String? {
let imagePath = (documentsPath as NSString).appendingPathComponent("\(name).jpg")
if let imageData = image.jpegData(compressionQuality: 0.8) {
do {
try imageData.write(to: URL(fileURLWithPath: imagePath))
print("💾 图片已保存到文件: \(imagePath)")
return imagePath
} catch {
print("❌ 保存图片失败: \(error)")
return nil
}
}
return nil
}
///
func loadImageFromFile(path: String) -> UIImage? {
return UIImage(contentsOfFile: path)
}
///
func deleteImageFromFile(path: String) {
do {
try fileManager.removeItem(atPath: path)
print("🗑️ 图片文件已删除: \(path)")
} catch {
print("❌ 删除图片文件失败: \(error)")
}
}
///
func cleanupExpiredFiles() {
let imageCachePath = (documentsPath as NSString).appendingPathComponent("ImageCache")
do {
let files = try fileManager.contentsOfDirectory(atPath: imageCachePath)
let currentDate = Date()
for file in files {
let filePath = (imageCachePath as NSString).appendingPathComponent(file)
let attributes = try fileManager.attributesOfItem(atPath: filePath)
if let creationDate = attributes[.creationDate] as? Date {
// 7
if currentDate.timeIntervalSince(creationDate) > 7 * 24 * 60 * 60 {
try fileManager.removeItem(atPath: filePath)
print("🗑️ 删除过期文件: \(file)")
}
}
}
} catch {
print("❌ 清理过期文件失败: \(error)")
}
}
// MARK: -
///
func getImageIntelligently(forKey key: String) -> UIImage? {
// 1.
if let cachedImage = getImage(forKey: key) {
return cachedImage
}
// 2.
let imagePath = (documentsPath as NSString).appendingPathComponent("\(key).jpg")
if let fileImage = loadImageFromFile(path: imagePath) {
//
setImage(fileImage, forKey: key)
return fileImage
}
return nil
}
///
func saveImageIntelligently(_ image: UIImage, forKey key: String) {
// 1.
setImage(image, forKey: key)
// 2.
_ = saveImageToFile(image, withName: key)
}
// MARK: -
@objc private func handleMemoryWarning() {
print("🚨 收到内存警告,清理图片缓存")
clearCache()
}
// MARK: -
///
func getCacheStatistics() -> (memoryCount: Int, memorySize: String, fileCount: Int) {
let memoryCount = cache.totalCostLimit > 0 ? cache.totalCostLimit : 0
let memorySize = ByteCountFormatter.string(fromByteCount: Int64(memoryCount), countStyle: .memory)
let imageCachePath = (documentsPath as NSString).appendingPathComponent("ImageCache")
var fileCount = 0
do {
let files = try fileManager.contentsOfDirectory(atPath: imageCachePath)
fileCount = files.count
} catch {
fileCount = 0
}
return (memoryCount, memorySize, fileCount)
}
///
func printCacheStatistics() {
let stats = getCacheStatistics()
print("📊 图片缓存统计:")
print(" - 内存缓存数量: \(stats.memoryCount)")
print(" - 内存缓存大小: \(stats.memorySize)")
print(" - 文件缓存数量: \(stats.fileCount)")
}
}
// MARK: -
extension ImageCacheManager {
///
func compressImage(_ image: UIImage, maxSize: CGSize) -> UIImage {
let originalSize = image.size
//
if originalSize.width <= maxSize.width && originalSize.height <= maxSize.height {
return image
}
//
let scaleX = maxSize.width / originalSize.width
let scaleY = maxSize.height / originalSize.height
let scale = min(scaleX, scaleY)
let newSize = CGSize(width: originalSize.width * scale, height: originalSize.height * scale)
//
let renderer = UIGraphicsImageRenderer(size: newSize)
return renderer.image { context in
image.draw(in: CGRect(origin: .zero, size: newSize))
}
}
///
func compressImageToFileSize(_ image: UIImage, targetSizeInKB: Double) -> UIImage {
let targetSizeInBytes = Int64(targetSizeInKB * 1024)
//
let compressionQualities: [CGFloat] = [0.8, 0.6, 0.4, 0.2, 0.1, 0.05]
for quality in compressionQualities {
if let imageData = image.jpegData(compressionQuality: quality) {
let dataSize = Int64(imageData.count)
if dataSize <= targetSizeInBytes {
if let compressedImage = UIImage(data: imageData) {
print("✅ 图片压缩成功: \(dataSize) bytes (质量: \(quality))")
return compressedImage
}
}
}
}
// JPEG
print("⚠️ JPEG压缩后仍超过目标大小尝试缩小尺寸")
return compressImageByReducingSize(image, targetSizeInBytes: targetSizeInBytes)
}
///
private func compressImageByReducingSize(_ image: UIImage, targetSizeInBytes: Int64) -> UIImage {
let originalSize = image.size
let originalWidth = originalSize.width
let originalHeight = originalSize.height
//
let currentMemorySize = Int64(originalWidth * originalHeight * 4) // RGBA
//
let scaleFactor = sqrt(Double(targetSizeInBytes) / Double(currentMemorySize))
let newWidth = max(originalWidth * CGFloat(scaleFactor), 40) // 40
let newHeight = max(originalHeight * CGFloat(scaleFactor), 40)
let newSize = CGSize(width: newWidth, height: newHeight)
//
let renderer = UIGraphicsImageRenderer(size: newSize)
let resizedImage = renderer.image { context in
image.draw(in: CGRect(origin: .zero, size: newSize))
}
// JPEG
if let imageData = resizedImage.jpegData(compressionQuality: 0.3) {
let finalSize = Int64(imageData.count)
print("✅ 通过缩小尺寸压缩成功: \(finalSize) bytes (新尺寸: \(newWidth) x \(newHeight))")
if let finalImage = UIImage(data: imageData) {
return finalImage
}
}
//
print("⚠️ 无法压缩到目标大小,返回最小尺寸图片")
let minSize = CGSize(width: 40, height: 40)
let rendererMin = UIGraphicsImageRenderer(size: minSize)
return rendererMin.image { context in
image.draw(in: CGRect(origin: .zero, size: minSize))
}
}
}