// // MapAmapMarkPictureLoadMananger.swift // map_amap_ios // // Created by 诺诺诺的言 on 2025/7/22. // import Foundation import UIKit // 图片加载进度回调 typealias ImageLoadingProgress = (Double) -> Void // 图片加载完成回调 typealias ImageCompletion = (UIImage?, Error?) -> Void /// 头像样式 enum AvatarStyle { case circle // 圆形 case rounded(radius: CGFloat) // 圆角矩形 } /// 图片加载工具类(含缓存功能,支持头像专用圆角处理) class MapAmapMarkPictureLoadMananger { // 单例模式 static let shared = MapAmapMarkPictureLoadMananger() // 内存缓存(LRU策略) private let memoryCache = NSCache() // 磁盘缓存路径 private let diskCachePath: String // 最大内存缓存大小(默认100MB) private let maxMemoryCacheSize: Int64 = 100 * 1024 * 1024 // 最大磁盘缓存大小(默认200MB) private let maxDiskCacheSize: Int64 = 200 * 1024 * 1024 // 初始化 private init() { // 创建磁盘缓存目录 let documentsPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0] diskCachePath = "\(documentsPath)/ImageCache" // 配置内存缓存 memoryCache.countLimit = 100 // 最多缓存100张图片 memoryCache.totalCostLimit = Int(maxMemoryCacheSize) // 清理过期的磁盘缓存 cleanExpiredDiskCache() } /// 加载头像图片(带缓存) func loadAvatar( from url: URL, placeholder: UIImage? = nil, style: AvatarStyle = .circle, progress: ImageLoadingProgress? = nil, completion: @escaping ImageCompletion ) -> UIImage? { // 生成缓存键(包含样式信息) let cacheKey = generateAvatarCacheKey(url: url, style: style) // 先检查内存缓存 if let cachedImage = memoryCache.object(forKey: cacheKey) { return cachedImage } // 检查磁盘缓存 if let cachedImage = loadAvatarFromDiskCache(url: url, style: style) { memoryCache.setObject(cachedImage, forKey: cacheKey) return cachedImage } // 显示占位图 completion(placeholder, nil) return placeholder } /// 异步加载头像图片(带进度监听) func loadAvatarAsync( from url: URL, placeholder: UIImage? = nil, style: AvatarStyle = .circle, progress: ImageLoadingProgress? = nil, completion: @escaping ImageCompletion ) { // 生成缓存键(包含样式信息) let cacheKey = generateAvatarCacheKey(url: url, style: style) // 先检查内存缓存 if let cachedImage = memoryCache.object(forKey: cacheKey) { DispatchQueue.main.async { completion(cachedImage, nil) } return } // 检查磁盘缓存 if let cachedImage = loadAvatarFromDiskCache(url: url, style: style) { memoryCache.setObject(cachedImage, forKey: cacheKey) DispatchQueue.main.async { completion(cachedImage, nil) } return } // 显示占位图 DispatchQueue.main.async { completion(placeholder, nil) } // 从网络加载图片 let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in guard let self = self, let data = data, error == nil, let response = response as? HTTPURLResponse, response.statusCode == 200, let image = UIImage(data: data) else { DispatchQueue.main.async { completion(nil, error) } return } // 应用头像样式处理 let processedImage = self.processAvatar(image: image, style: style) // 保存到缓存 self.saveAvatarToCache(image: processedImage, url: url, style: style) DispatchQueue.main.async { completion(processedImage, nil) } } task.resume() } /// 生成头像缓存键 private func generateAvatarCacheKey(url: URL, style: AvatarStyle) -> NSString { let styleKey: String switch style { case .circle: styleKey = "circle" case .rounded(let radius): styleKey = "rounded_\(radius)" } return "\(url.absoluteString)_\(styleKey)" as NSString } /// 从磁盘缓存加载头像 private func loadAvatarFromDiskCache(url: URL, style: AvatarStyle) -> UIImage? { let fileName = generateAvatarFileName(url: url, style: style) let filePath = "\(diskCachePath)/\(fileName)" if let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) { return UIImage(data: data) } return nil } /// 生成头像文件名 private func generateAvatarFileName(url: URL, style: AvatarStyle) -> String { let baseFileName = url.lastPathComponent let styleKey: String switch style { case .circle: styleKey = "avatar_circle" case .rounded(let radius): styleKey = "avatar_rounded_\(radius)" } return "\(styleKey)_\(baseFileName)" } /// 保存头像到缓存 private func saveAvatarToCache(image: UIImage, url: URL, style: AvatarStyle) { // 保存到内存缓存 let cacheKey = generateAvatarCacheKey(url: url, style: style) memoryCache.setObject(image, forKey: cacheKey) // 保存到磁盘缓存 DispatchQueue.global(qos: .background).async { [weak self] in guard let self = self else { return } // 创建缓存目录(如果不存在) if !FileManager.default.fileExists(atPath: self.diskCachePath) { do { try FileManager.default.createDirectory(atPath: self.diskCachePath, withIntermediateDirectories: true) } catch { return } } let fileName = self.generateAvatarFileName(url: url, style: style) let filePath = "\(self.diskCachePath)/\(fileName)" // 压缩图片(JPEG格式,质量0.8) if let data = image.jpegData(compressionQuality: 0.8) { do { try data.write(to: URL(fileURLWithPath: filePath), options: .atomic) self.updateDiskCacheSize() } catch { //print("保存头像到磁盘失败: \(error)") } } } } /// 处理头像样式 private func processAvatar(image: UIImage, style: AvatarStyle) -> UIImage { // 优化:如果是圆形且图片已经是正方形,直接使用CoreImage滤镜 if case .circle = style, image.size.width == image.size.height { return applyCircularFilter(image: image) } // 否则使用传统绘图方式 return applyAvatarStyle(image: image, style: style) } /// 应用圆形滤镜(性能优化) private func applyCircularFilter(image: UIImage) -> UIImage { guard let cgImage = image.cgImage, let filter = CIFilter(name: "CICropToCircle") else { return image } let ciImage = CIImage(cgImage: cgImage) filter.setValue(ciImage, forKey: kCIInputImageKey) if let outputImage = filter.outputImage, let outputCGImage = CIContext().createCGImage(outputImage, from: outputImage.extent) { return UIImage(cgImage: outputCGImage, scale: image.scale, orientation: image.imageOrientation) } return image } /// 应用头像样式(传统绘图方式) private func applyAvatarStyle(image: UIImage, style: AvatarStyle) -> UIImage { let size = image.size let rect = CGRect(origin: .zero, size: size) let cornerRadius: CGFloat switch style { case .circle: cornerRadius = min(size.width, size.height) / 2 case .rounded(let radius): cornerRadius = radius } UIGraphicsBeginImageContextWithOptions(size, false, image.scale) defer { UIGraphicsEndImageContext() } let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius) path.addClip() image.draw(in: rect) if let outputImage = UIGraphicsGetImageFromCurrentImageContext() { return outputImage } return image } /// 更新磁盘缓存大小 private func updateDiskCacheSize() { // 保持原有逻辑不变... do { let fileManager = FileManager.default let cacheFiles = try fileManager.contentsOfDirectory(atPath: diskCachePath) var totalSize: Int64 = 0 for file in cacheFiles { let filePath = "\(diskCachePath)/\(file)" if let attributes = try? fileManager.attributesOfItem(atPath: filePath), let size = attributes[.size] as? Int64 { totalSize += size } } // 如果超过最大缓存大小,清理旧文件 if totalSize > maxDiskCacheSize { cleanOldestDiskCacheFiles(untilSize: totalSize - maxDiskCacheSize) } } catch { //print("更新磁盘缓存大小失败: \(error)") } } /// 清理最旧的磁盘缓存文件 private func cleanOldestDiskCacheFiles(untilSize: Int64) { // 保持原有逻辑不变... guard untilSize > 0 else { return } do { let fileManager = FileManager.default var files: [(path: String, date: Date)] = [] // 获取所有缓存文件及其修改日期 for file in try fileManager.contentsOfDirectory(atPath: diskCachePath) { let filePath = "\(diskCachePath)/\(file)" if let attributes = try? fileManager.attributesOfItem(atPath: filePath), let date = attributes[.modificationDate] as? Date, let size = attributes[.size] as? Int64, size > 0 { files.append((path: filePath, date: date)) } } // 按修改日期排序(最旧的在前) files.sort { $0.date < $1.date } // 删除最旧的文件,直到达到目标大小 var removedSize: Int64 = 0 for file in files { if removedSize >= untilSize { break } do { let attributes = try fileManager.attributesOfItem(atPath: file.path) if let size = attributes[.size] as? Int64 { try fileManager.removeItem(atPath: file.path) removedSize += size } } catch { //print("删除缓存文件失败: \(error)") } } } catch { //print("清理磁盘缓存失败: \(error)") } } /// 清理过期的磁盘缓存 private func cleanExpiredDiskCache() { // 保持原有逻辑不变... // 这里可以添加清理逻辑,例如删除超过30天的缓存 let oneMonthAgo = Date().addingTimeInterval(-30 * 24 * 60 * 60) do { let fileManager = FileManager.default for file in try fileManager.contentsOfDirectory(atPath: diskCachePath) { let filePath = "\(diskCachePath)/\(file)" if let attributes = try? fileManager.attributesOfItem(atPath: filePath), let date = attributes[.modificationDate] as? Date, date < oneMonthAgo { try fileManager.removeItem(atPath: filePath) } } } catch { //print("清理过期缓存失败: \(error)") } } /// 清除所有缓存 func clearAllCache(completion: (() -> Void)? = nil) { // 保持原有逻辑不变... // 清除内存缓存 memoryCache.removeAllObjects() // 清除磁盘缓存 DispatchQueue.global(qos: .background).async { [weak self] in guard let self = self else { return } do { if FileManager.default.fileExists(atPath: self.diskCachePath) { try FileManager.default.removeItem(atPath: self.diskCachePath) // 重新创建空目录 try FileManager.default.createDirectory(atPath: self.diskCachePath, withIntermediateDirectories: true) } } catch { //print("清除磁盘缓存失败: \(error)") } DispatchQueue.main.async { completion?() } } } }