| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- //
- // 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<NSString, UIImage>()
- // 磁盘缓存路径
- 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?()
- }
- }
- }
- }
|