import UIKit import Foundation import Combine // MARK: - 图片缓存管理器 class ImageCacheManager: ObservableObject { static let shared = ImageCacheManager() private let cache = NSCache() 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)) } } }