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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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))
}
}
}