活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

Swift UIImage尺寸管理终极指南获取调整优化内存占用解决卡顿问题打造流畅用户体验

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-8 20:40:01 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

在iOS应用开发中,图片是UI设计中不可或缺的元素。然而,不恰当的图片处理方式可能导致内存占用过高、界面卡顿,甚至应用崩溃。本文将全面介绍如何在Swift中高效管理UIImage的尺寸,从而优化内存使用,解决性能问题,为用户提供流畅的体验。

UIImage基础

UIImage是iOS开发中用于表示图像的核心类。了解UIImage的基本特性对于有效管理图片尺寸至关重要。
  1. // 创建一个简单的UIImage实例
  2. let image = UIImage(named: "example_image")
复制代码

每个UIImage对象都包含图像数据以及与其关联的元数据,如尺寸、缩放比例和方向。以下是UIImage的一些关键属性:
  1. if let image = UIImage(named: "example_image") {
  2.     // 图片的尺寸(以点为单位)
  3.     let size = image.size
  4.     print("Image size: \(size.width) x \(size.height)")
  5.    
  6.     // 图片的缩放比例
  7.     let scale = image.scale
  8.     print("Image scale: \(scale)")
  9.    
  10.     // 图片的实际像素尺寸
  11.     let pixelSize = CGSize(width: size.width * scale, height: size.height * scale)
  12.     print("Image pixel size: \(pixelSize.width) x \(pixelSize.height)")
  13.    
  14.     // 图片的方向
  15.     let orientation = image.imageOrientation
  16.     print("Image orientation: \(orientation.rawValue)")
  17. }
复制代码

获取图片尺寸

获取图片尺寸是图片处理的第一步。Swift提供了多种方式来获取图片尺寸信息。

从资源文件获取图片尺寸
  1. // 方法1:直接创建UIImage并获取尺寸
  2. if let image = UIImage(named: "example_image") {
  3.     let size = image.size
  4.     print("Image size: \(size.width) x \(size.height)")
  5. }
  6. // 方法2:通过UIImageAsset获取(适用于多分辨率图片)
  7. if let asset = UIImageAsset(named: "example_image") {
  8.     let image = asset.image(with: UITraitCollection(displayScale: UIScreen.main.scale))
  9.     let size = image.size
  10.     print("Image size: \(size.width) x \(size.height)")
  11. }
复制代码

从文件URL获取图片尺寸
  1. func imageSize(at url: URL) -> CGSize? {
  2.     // 使用CGImageSource获取图片信息而不完全加载图片
  3.     guard let source = CGImageSourceCreateWithURL(url, nil),
  4.           let metadata = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [String: Any] else {
  5.         return nil
  6.     }
  7.    
  8.     // 获取像素尺寸
  9.     let pixelWidth = metadata[kCGImagePropertyPixelWidth as String] as? CGFloat ?? 0
  10.     let pixelHeight = metadata[kCGImagePropertyPixelHeight as String] as? CGFloat ?? 0
  11.    
  12.     // 获取图片的DPI(用于计算逻辑尺寸)
  13.     let dpiWidth = metadata[kCGImagePropertyDPIWidth as String] as? CGFloat ?? 72
  14.     let dpiHeight = metadata[kCGImagePropertyDPIHeight as String] as? CGFloat ?? 72
  15.    
  16.     // 计算逻辑尺寸(点)
  17.     let pointWidth = pixelWidth * 72 / dpiWidth
  18.     let pointHeight = pixelHeight * 72 / dpiHeight
  19.    
  20.     return CGSize(width: pointWidth, height: pointHeight)
  21. }
  22. // 使用示例
  23. if let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
  24.     let imageURL = documentsURL.appendingPathComponent("example.jpg")
  25.     if let size = imageSize(at: imageURL) {
  26.         print("Image size: \(size.width) x \(size.height)")
  27.     }
  28. }
复制代码

从网络获取图片尺寸
  1. import ImageIO
  2. func getImageSizeFromURL(_ url: URL, completion: @escaping (CGSize?) -> Void) {
  3.     let task = URLSession.shared.dataTask(with: url) { data, response, error in
  4.         guard let data = data, error == nil else {
  5.             completion(nil)
  6.             return
  7.         }
  8.         
  9.         // 使用CGImageSource获取图片信息而不完全解码图片
  10.         if let source = CGImageSourceCreateWithData(data as CFData, nil),
  11.            let metadata = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [String: Any] {
  12.             
  13.             let pixelWidth = metadata[kCGImagePropertyPixelWidth as String] as? CGFloat ?? 0
  14.             let pixelHeight = metadata[kCGImagePropertyPixelHeight as String] as? CGFloat ?? 0
  15.             
  16.             DispatchQueue.main.async {
  17.                 completion(CGSize(width: pixelWidth, height: pixelHeight))
  18.             }
  19.         } else {
  20.             DispatchQueue.main.async {
  21.                 completion(nil)
  22.             }
  23.         }
  24.     }
  25.     task.resume()
  26. }
  27. // 使用示例
  28. if let url = URL(string: "https://example.com/image.jpg") {
  29.     getImageSizeFromURL(url) { size in
  30.         if let size = size {
  31.             print("Image size: \(size.width) x \(size.height)")
  32.         } else {
  33.             print("Failed to get image size")
  34.         }
  35.     }
  36. }
复制代码

图片尺寸调整

调整图片尺寸是优化内存使用和提升性能的关键步骤。以下是几种常见的图片尺寸调整方法。

简单的尺寸调整
  1. extension UIImage {
  2.     func resized(to size: CGSize) -> UIImage? {
  3.         UIGraphicsBeginImageContextWithOptions(size, false, scale)
  4.         defer { UIGraphicsEndImageContext() }
  5.         
  6.         draw(in: CGRect(origin: .zero, size: size))
  7.         return UIGraphicsGetImageFromCurrentImageContext()
  8.     }
  9.    
  10.     func resized(toWidth width: CGFloat) -> UIImage? {
  11.         let height = width * (size.height / size.width)
  12.         return resized(to: CGSize(width: width, height: height))
  13.     }
  14.    
  15.     func resized(toHeight height: CGFloat) -> UIImage? {
  16.         let width = height * (size.width / size.height)
  17.         return resized(to: CGSize(width: width, height: height))
  18.     }
  19. }
  20. // 使用示例
  21. if let originalImage = UIImage(named: "large_image") {
  22.     // 调整到指定尺寸
  23.     if let resizedImage = originalImage.resized(to: CGSize(width: 200, height: 200)) {
  24.         // 使用调整后的图片
  25.     }
  26.    
  27.     // 按宽度调整,保持宽高比
  28.     if let widthResizedImage = originalImage.resized(toWidth: 300) {
  29.         // 使用调整后的图片
  30.     }
  31.    
  32.     // 按高度调整,保持宽高比
  33.     if let heightResizedImage = originalImage.resized(toHeight: 300) {
  34.         // 使用调整后的图片
  35.     }
  36. }
复制代码

高质量的尺寸调整
  1. extension UIImage {
  2.     func highQualityResized(to size: CGSize) -> UIImage? {
  3.         let renderer = UIGraphicsImageRenderer(size: size)
  4.         return renderer.image { _ in
  5.             self.draw(in: CGRect(origin: .zero, size: size))
  6.         }
  7.     }
  8.    
  9.     func highQualityResized(toWidth width: CGFloat) -> UIImage? {
  10.         let height = width * (size.height / size.width)
  11.         return highQualityResized(to: CGSize(width: width, height: height))
  12.     }
  13.    
  14.     func highQualityResized(toHeight height: CGFloat) -> UIImage? {
  15.         let width = height * (size.width / size.height)
  16.         return highQualityResized(to: CGSize(width: width, height: height))
  17.     }
  18. }
  19. // 使用示例
  20. if let originalImage = UIImage(named: "large_image") {
  21.     if let highQualityResizedImage = originalImage.highQualityResized(toWidth: 300) {
  22.         // 使用高质量调整后的图片
  23.     }
  24. }
复制代码

使用Core Image进行高级调整
  1. import CoreImage
  2. import CoreImage.CIFilterBuiltins
  3. extension UIImage {
  4.     func resizedUsingCoreImage(to size: CGSize) -> UIImage? {
  5.         guard let cgImage = self.cgImage else { return nil }
  6.         
  7.         let ciImage = CIImage(cgImage: cgImage)
  8.         let context = CIContext()
  9.         
  10.         // 计算缩放比例
  11.         let scaleX = size.width / self.size.width
  12.         let scaleY = size.height / self.size.height
  13.         
  14.         // 创建变换并应用缩放
  15.         let transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
  16.         let scaledImage = ciImage.transformed(by: transform)
  17.         
  18.         // 渲染结果
  19.         if let outputCGImage = context.createCGImage(scaledImage, from: scaledImage.extent) {
  20.             return UIImage(cgImage: outputCGImage, scale: self.scale, orientation: self.imageOrientation)
  21.         }
  22.         
  23.         return nil
  24.     }
  25.    
  26.     func resizedUsingCoreImage(toWidth width: CGFloat) -> UIImage? {
  27.         let height = width * (size.height / size.width)
  28.         return resizedUsingCoreImage(to: CGSize(width: width, height: height))
  29.     }
  30. }
  31. // 使用示例
  32. if let originalImage = UIImage(named: "large_image") {
  33.     if let coreImageResized = originalImage.resizedUsingCoreImage(toWidth: 300) {
  34.         // 使用Core Image调整后的图片
  35.     }
  36. }
复制代码

使用vImage进行高性能调整
  1. import Accelerate
  2. extension UIImage {
  3.     func resizedUsingvImage(to size: CGSize) -> UIImage? {
  4.         guard let cgImage = self.cgImage else { return nil }
  5.         
  6.         var sourceBuffer = vImage_Buffer()
  7.         defer {
  8.             if sourceBuffer.data != nil {
  9.                 free(sourceBuffer.data)
  10.             }
  11.         }
  12.         
  13.         var destinationBuffer = vImage_Buffer()
  14.         defer {
  15.             if destinationBuffer.data != nil {
  16.                 free(destinationBuffer.data)
  17.             }
  18.         }
  19.         
  20.         var format = vImage_CGImageFormat(bitsPerComponent: 8,
  21.                                          bitsPerPixel: 32,
  22.                                          colorSpace: CGColorSpaceCreateDeviceRGB(),
  23.                                          bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),
  24.                                          version: 0,
  25.                                          decode: nil,
  26.                                          renderingIntent: .defaultIntent)
  27.         
  28.         var error = vImageBuffer_InitWithCGImage(&sourceBuffer, &format, nil, cgImage, .noFlags)
  29.         guard error == kvImageNoError else { return nil }
  30.         
  31.         error = vImageBuffer_Init(&destinationBuffer, vImagePixelCount(size.height), vImagePixelCount(size.width), format.bitsPerPixel, .noFlags)
  32.         guard error == kvImageNoError else { return nil }
  33.         
  34.         // 执行缩放
  35.         error = vImageScale_ARGB8888(&sourceBuffer, &destinationBuffer, nil, .noFlags)
  36.         guard error == kvImageNoError else { return nil }
  37.         
  38.         // 创建输出图像
  39.         guard let resizedCGImage = vImageCreateCGImageFromBuffer(&destinationBuffer, &format, nil, nil, .noFlags, &error)?.takeRetainedValue(),
  40.               error == kvImageNoError else {
  41.             return nil
  42.         }
  43.         
  44.         return UIImage(cgImage: resizedCGImage, scale: self.scale, orientation: self.imageOrientation)
  45.     }
  46. }
  47. // 使用示例
  48. if let originalImage = UIImage(named: "large_image") {
  49.     if let vImageResized = originalImage.resizedUsingvImage(to: CGSize(width: 300, height: 300)) {
  50.         // 使用vImage调整后的图片
  51.     }
  52. }
复制代码

内存优化

优化UIImage的内存占用对于防止应用崩溃和提升性能至关重要。以下是几种优化内存使用的方法。

计算图片内存占用
  1. extension UIImage {
  2.     var estimatedMemoryUsage: Int {
  3.         // 计算图片的字节大小
  4.         let bytesPerPixel = 4 // 假设每个像素使用4字节(RGBA)
  5.         let widthInPixels = Int(size.width * scale)
  6.         let heightInPixels = Int(size.height * scale)
  7.         return widthInPixels * heightInPixels * bytesPerPixel
  8.     }
  9. }
  10. // 使用示例
  11. if let image = UIImage(named: "large_image") {
  12.     let memoryUsage = image.estimatedMemoryUsage
  13.     print("Estimated memory usage: \(memoryUsage / (1024 * 1024)) MB")
  14. }
复制代码

使用适当格式的图片
  1. // PNG格式 - 适合带有透明度的图片
  2. func loadPNGImage(named name: String) -> UIImage? {
  3.     return UIImage(named: name)
  4. }
  5. // JPEG格式 - 适合照片类图片,可以压缩
  6. func loadJPEGImage(named name: String, compressionQuality: CGFloat = 0.8) -> UIImage? {
  7.     guard let imagePath = Bundle.main.path(forResource: name, ofType: "jpg"),
  8.           let imageData = try? Data(contentsOf: URL(fileURLWithPath: imagePath)),
  9.           let image = UIImage(data: imageData) else {
  10.         return nil
  11.     }
  12.     return image
  13. }
  14. // WebP格式 - 更高效的图片格式,需要第三方库支持
  15. func loadWebPImage(named name: String) -> UIImage? {
  16.     // 这里需要使用第三方库如SDWebImage或Kingfisher来加载WebP图片
  17.     // 示例代码假设使用SDWebImage
  18.     if let imagePath = Bundle.main.path(forResource: name, ofType: "webp") {
  19.         return SDWebImageManager.shared.imageCache?.imageFromDiskCache(forKey: imagePath)
  20.     }
  21.     return nil
  22. }
复制代码

使用图像降采样
  1. extension UIImage {
  2.     func downsample(to size: CGSize, scale: CGFloat = UIScreen.main.scale) -> UIImage? {
  3.         guard let imageData = self.jpegData(compressionQuality: 1.0) ?? self.pngData() else { return nil }
  4.         
  5.         let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
  6.         guard let imageSource = CGImageSourceCreateWithData(imageData as CFData, imageSourceOptions) else { return nil }
  7.         
  8.         let maxDimensionInPixels = max(size.width, size.height) * scale
  9.         
  10.         let downsampleOptions = [
  11.             kCGImageSourceCreateThumbnailFromImageAlways: true,
  12.             kCGImageSourceShouldCacheImmediately: true,
  13.             kCGImageSourceCreateThumbnailWithTransform: true,
  14.             kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
  15.         ] as CFDictionary
  16.         
  17.         guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { return nil }
  18.         
  19.         return UIImage(cgImage: downsampledImage, scale: scale, orientation: self.imageOrientation)
  20.     }
  21. }
  22. // 使用示例
  23. if let originalImage = UIImage(named: "large_image") {
  24.     if let downsampledImage = originalImage.downsample(to: CGSize(width: 200, height: 200)) {
  25.         // 使用降采样后的图片
  26.         print("Original image memory usage: \(originalImage.estimatedMemoryUsage / (1024 * 1024)) MB")
  27.         print("Downsampled image memory usage: \(downsampledImage.estimatedMemoryUsage / (1024 * 1024)) MB")
  28.     }
  29. }
复制代码

使用图像池和缓存策略
  1. class ImageManager {
  2.     static let shared = ImageManager()
  3.     private var imageCache = NSCache<NSString, UIImage>()
  4.    
  5.     private init() {
  6.         // 设置缓存限制
  7.         let memoryCapacity = 100 * 1024 * 1024 // 100MB
  8.         let preferredMemoryCapacity = 50 * 1024 * 1024 // 50MB
  9.         imageCache.totalCostLimit = memoryCapacity
  10.         imageCache.countLimit = 50 // 最多缓存50张图片
  11.         
  12.         // 监听内存警告
  13.         NotificationCenter.default.addObserver(
  14.             self,
  15.             selector: #selector(clearCache),
  16.             name: UIApplication.didReceiveMemoryWarningNotification,
  17.             object: nil
  18.         )
  19.     }
  20.    
  21.     @objc private func clearCache() {
  22.         imageCache.removeAllObjects()
  23.     }
  24.    
  25.     func loadImage(named name: String, size: CGSize? = nil) -> UIImage? {
  26.         let cacheKey = "\(name)-\(size?.width ?? 0)-\(size?.height ?? 0)" as NSString
  27.         
  28.         // 检查缓存
  29.         if let cachedImage = imageCache.object(forKey: cacheKey) {
  30.             return cachedImage
  31.         }
  32.         
  33.         // 加载图片
  34.         guard var image = UIImage(named: name) else { return nil }
  35.         
  36.         // 调整尺寸
  37.         if let size = size {
  38.             image = image.resized(to: size) ?? image
  39.         }
  40.         
  41.         // 缓存图片
  42.         imageCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
  43.         
  44.         return image
  45.     }
  46.    
  47.     func preloadImages(names: [String], size: CGSize? = nil) {
  48.         DispatchQueue.global(qos: .background).async {
  49.             for name in names {
  50.                 _ = self.loadImage(named: name, size: size)
  51.             }
  52.         }
  53.     }
  54. }
  55. // 使用示例
  56. let imageManager = ImageManager.shared
  57. // 加载图片
  58. if let image = imageManager.loadImage(named: "example_image", size: CGSize(width: 200, height: 200)) {
  59.     // 使用图片
  60. }
  61. // 预加载图片
  62. imageManager.preloadImages(names: ["image1", "image2", "image3"], size: CGSize(width: 100, height: 100))
复制代码

解决卡顿问题

图片处理中的卡顿问题通常是由于主线程阻塞或内存压力导致的。以下是几种解决卡顿问题的方法。

异步图片加载和处理
  1. class AsyncImageLoader {
  2.     static let shared = AsyncImageLoader()
  3.     private var imageCache = NSCache<NSString, UIImage>()
  4.     private var taskCache = [NSString: URLSessionDataTask]()
  5.    
  6.     private init() {
  7.         imageCache.totalCostLimit = 100 * 1024 * 1024 // 100MB
  8.         NotificationCenter.default.addObserver(
  9.             self,
  10.             selector: #selector(clearCache),
  11.             name: UIApplication.didReceiveMemoryWarningNotification,
  12.             object: nil
  13.         )
  14.     }
  15.    
  16.     @objc private func clearCache() {
  17.         imageCache.removeAllObjects()
  18.         taskCache.values.forEach { $0.cancel() }
  19.         taskCache.removeAll()
  20.     }
  21.    
  22.     func loadImage(from url: URL, size: CGSize? = nil, completion: @escaping (UIImage?) -> Void) {
  23.         let cacheKey = url.absoluteString as NSString
  24.         
  25.         // 检查缓存
  26.         if let cachedImage = imageCache.object(forKey: cacheKey) {
  27.             completion(cachedImage)
  28.             return
  29.         }
  30.         
  31.         // 检查是否有正在进行的任务
  32.         if let existingTask = taskCache[cacheKey] {
  33.             // 如果已有任务在加载此图片,则不再创建新任务
  34.             return
  35.         }
  36.         
  37.         // 创建并启动数据任务
  38.         let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
  39.             guard let self = self else { return }
  40.             
  41.             // 移除任务缓存
  42.             self.taskCache.removeValue(forKey: cacheKey)
  43.             
  44.             guard let data = data, error == nil, let image = UIImage(data: data) else {
  45.                 DispatchQueue.main.async {
  46.                     completion(nil)
  47.                 }
  48.                 return
  49.             }
  50.             
  51.             // 调整尺寸
  52.             var finalImage = image
  53.             if let size = size {
  54.                 finalImage = image.resized(to: size) ?? image
  55.             }
  56.             
  57.             // 缓存图片
  58.             self.imageCache.setObject(finalImage, forKey: cacheKey, cost: finalImage.estimatedMemoryUsage)
  59.             
  60.             DispatchQueue.main.async {
  61.                 completion(finalImage)
  62.             }
  63.         }
  64.         
  65.         // 缓存任务
  66.         taskCache[cacheKey] = task
  67.         task.resume()
  68.     }
  69.    
  70.     func cancelLoad(for url: URL) {
  71.         let cacheKey = url.absoluteString as NSString
  72.         taskCache[cacheKey]?.cancel()
  73.         taskCache.removeValue(forKey: cacheKey)
  74.     }
  75. }
  76. // 使用示例
  77. let loader = AsyncImageLoader.shared
  78. if let url = URL(string: "https://example.com/image.jpg") {
  79.     loader.loadImage(from: url, size: CGSize(width: 200, height: 200)) { image in
  80.         if let image = image {
  81.             // 在主线程更新UI
  82.             DispatchQueue.main.async {
  83.                 self.imageView.image = image
  84.             }
  85.         }
  86.     }
  87. }
复制代码

使用预加载和懒加载策略
  1. class ImagePreloader {
  2.     static let shared = ImagePreloader()
  3.     private var preloadQueue = DispatchQueue(label: "com.example.imagePreloader", qos: .background)
  4.     private var imageCache = NSCache<NSString, UIImage>()
  5.    
  6.     private init() {
  7.         imageCache.totalCostLimit = 100 * 1024 * 1024 // 100MB
  8.         NotificationCenter.default.addObserver(
  9.             self,
  10.             selector: #selector(clearCache),
  11.             name: UIApplication.didReceiveMemoryWarningNotification,
  12.             object: nil
  13.         )
  14.     }
  15.    
  16.     @objc private func clearCache() {
  17.         imageCache.removeAllObjects()
  18.     }
  19.    
  20.     func preloadImages(from urls: [URL], size: CGSize? = nil) {
  21.         preloadQueue.async {
  22.             for url in urls {
  23.                 let cacheKey = url.absoluteString as NSString
  24.                
  25.                 // 如果图片已经在缓存中,则跳过
  26.                 if self.imageCache.object(forKey: cacheKey) != nil {
  27.                     continue
  28.                 }
  29.                
  30.                 // 下载图片
  31.                 if let data = try? Data(contentsOf: url),
  32.                    var image = UIImage(data: data) {
  33.                     
  34.                     // 调整尺寸
  35.                     if let size = size {
  36.                         image = image.resized(to: size) ?? image
  37.                     }
  38.                     
  39.                     // 缓存图片
  40.                     self.imageCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
  41.                 }
  42.             }
  43.         }
  44.     }
  45.    
  46.     func getImage(for url: URL) -> UIImage? {
  47.         let cacheKey = url.absoluteString as NSString
  48.         return imageCache.object(forKey: cacheKey)
  49.     }
  50. }
  51. // 使用示例
  52. let preloader = ImagePreloader.shared
  53. // 预加载图片
  54. let imageUrls = [
  55.     URL(string: "https://example.com/image1.jpg")!,
  56.     URL(string: "https://example.com/image2.jpg")!,
  57.     URL(string: "https://example.com/image3.jpg")!
  58. ]
  59. preloader.preloadImages(from: imageUrls, size: CGSize(width: 200, height: 200))
  60. // 在需要时获取图片
  61. if let url = URL(string: "https://example.com/image1.jpg"),
  62.    let preloadedImage = preloader.getImage(for: url) {
  63.     imageView.image = preloadedImage
  64. }
复制代码

使用占位图和渐进式加载
  1. class ProgressiveImageLoader {
  2.     static let shared = ProgressiveImageLoader()
  3.     private var imageCache = NSCache<NSString, UIImage>()
  4.    
  5.     private init() {
  6.         imageCache.totalCostLimit = 100 * 1024 * 1024 // 100MB
  7.         NotificationCenter.default.addObserver(
  8.             self,
  9.             selector: #selector(clearCache),
  10.             name: UIApplication.didReceiveMemoryWarningNotification,
  11.             object: nil
  12.         )
  13.     }
  14.    
  15.     @objc private func clearCache() {
  16.         imageCache.removeAllObjects()
  17.     }
  18.    
  19.     func loadImageWithPlaceholder(
  20.         from url: URL,
  21.         placeholder: UIImage? = nil,
  22.         size: CGSize? = nil,
  23.         completion: @escaping (UIImage?) -> Void
  24.     ) {
  25.         let cacheKey = url.absoluteString as NSString
  26.         
  27.         // 检查缓存
  28.         if let cachedImage = imageCache.object(forKey: cacheKey) {
  29.             completion(cachedImage)
  30.             return
  31.         }
  32.         
  33.         // 先显示占位图
  34.         DispatchQueue.main.async {
  35.             completion(placeholder)
  36.         }
  37.         
  38.         // 异步加载图片
  39.         DispatchQueue.global(qos: .background).async { [weak self] in
  40.             guard let self = self else { return }
  41.             
  42.             if let data = try? Data(contentsOf: url),
  43.                var image = UIImage(data: data) {
  44.                
  45.                 // 调整尺寸
  46.                 if let size = size {
  47.                     image = image.resized(to: size) ?? image
  48.                 }
  49.                
  50.                 // 缓存图片
  51.                 self.imageCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
  52.                
  53.                 DispatchQueue.main.async {
  54.                     completion(image)
  55.                 }
  56.             } else {
  57.                 DispatchQueue.main.async {
  58.                     completion(placeholder)
  59.                 }
  60.             }
  61.         }
  62.     }
  63. }
  64. // 使用示例
  65. let loader = ProgressiveImageLoader.shared
  66. let placeholder = UIImage(named: "placeholder")
  67. if let url = URL(string: "https://example.com/image.jpg") {
  68.     loader.loadImageWithPlaceholder(
  69.         from: url,
  70.         placeholder: placeholder,
  71.         size: CGSize(width: 200, height: 200)
  72.     ) { image in
  73.         self.imageView.image = image
  74.     }
  75. }
复制代码

使用图像解码优化
  1. extension UIImage {
  2.     func decodedImage() -> UIImage? {
  3.         guard let cgImage = cgImage else { return nil }
  4.         
  5.         let size = CGSize(width: cgImage.width, height: cgImage.height)
  6.         let colorSpace = CGColorSpaceCreateDeviceRGB()
  7.         let context = CGContext(
  8.             data: nil,
  9.             width: Int(size.width),
  10.             height: Int(size.height),
  11.             bitsPerComponent: 8,
  12.             bytesPerRow: 0,
  13.             space: colorSpace,
  14.             bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
  15.         )
  16.         
  17.         context?.draw(cgImage, in: CGRect(origin: .zero, size: size))
  18.         
  19.         guard let decodedImage = context?.makeImage() else { return nil }
  20.         
  21.         return UIImage(cgImage: decodedImage, scale: scale, orientation: imageOrientation)
  22.     }
  23. }
  24. class OptimizedImageLoader {
  25.     static let shared = OptimizedImageLoader()
  26.     private var imageCache = NSCache<NSString, UIImage>()
  27.     private let processingQueue = DispatchQueue(label: "com.example.imageProcessing", qos: .userInitiated)
  28.    
  29.     private init() {
  30.         imageCache.totalCostLimit = 100 * 1024 * 1024 // 100MB
  31.         NotificationCenter.default.addObserver(
  32.             self,
  33.             selector: #selector(clearCache),
  34.             name: UIApplication.didReceiveMemoryWarningNotification,
  35.             object: nil
  36.         )
  37.     }
  38.    
  39.     @objc private func clearCache() {
  40.         imageCache.removeAllObjects()
  41.     }
  42.    
  43.     func loadAndDecodeImage(
  44.         from url: URL,
  45.         size: CGSize? = nil,
  46.         completion: @escaping (UIImage?) -> Void
  47.     ) {
  48.         let cacheKey = url.absoluteString as NSString
  49.         
  50.         // 检查缓存
  51.         if let cachedImage = imageCache.object(forKey: cacheKey) {
  52.             completion(cachedImage)
  53.             return
  54.         }
  55.         
  56.         // 异步加载和解码图片
  57.         processingQueue.async { [weak self] in
  58.             guard let self = self else { return }
  59.             
  60.             if let data = try? Data(contentsOf: url),
  61.                var image = UIImage(data: data)?.decodedImage() {
  62.                
  63.                 // 调整尺寸
  64.                 if let size = size {
  65.                     image = image.resized(to: size) ?? image
  66.                 }
  67.                
  68.                 // 缓存图片
  69.                 self.imageCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
  70.                
  71.                 DispatchQueue.main.async {
  72.                     completion(image)
  73.                 }
  74.             } else {
  75.                 DispatchQueue.main.async {
  76.                     completion(nil)
  77.                 }
  78.             }
  79.         }
  80.     }
  81. }
  82. // 使用示例
  83. let loader = OptimizedImageLoader.shared
  84. if let url = URL(string: "https://example.com/image.jpg") {
  85.     loader.loadAndDecodeImage(from: url, size: CGSize(width: 200, height: 200)) { image in
  86.         self.imageView.image = image
  87.     }
  88. }
复制代码

打造流畅用户体验

除了技术层面的优化,用户体验也是图片处理的重要考量因素。以下是几种提升用户体验的方法。

实现平滑的图片过渡效果
  1. extension UIImageView {
  2.     func setImageWithTransition(
  3.         _ newImage: UIImage?,
  4.         duration: TimeInterval = 0.3,
  5.         options: UIView.AnimationOptions = .transitionCrossDissolve
  6.     ) {
  7.         UIView.transition(
  8.             with: self,
  9.             duration: duration,
  10.             options: options,
  11.             animations: {
  12.                 self.image = newImage
  13.             },
  14.             completion: nil
  15.         )
  16.     }
  17. }
  18. // 使用示例
  19. if let url = URL(string: "https://example.com/image.jpg") {
  20.     AsyncImageLoader.shared.loadImage(from: url, size: CGSize(width: 200, height: 200)) { image in
  21.         self.imageView.setImageWithTransition(image)
  22.     }
  23. }
复制代码

实现图片淡入效果
  1. extension UIImageView {
  2.     func setImageWithFade(_ newImage: UIImage?, duration: TimeInterval = 0.5) {
  3.         image = newImage
  4.         alpha = 0
  5.         
  6.         UIView.animate(withDuration: duration) {
  7.             self.alpha = 1
  8.         }
  9.     }
  10. }
  11. // 使用示例
  12. if let url = URL(string: "https://example.com/image.jpg") {
  13.     AsyncImageLoader.shared.loadImage(from: url, size: CGSize(width: 200, height: 200)) { image in
  14.         self.imageView.setImageWithFade(image)
  15.     }
  16. }
复制代码

实现渐进式图片加载
  1. class ProgressiveImageView: UIImageView {
  2.     private var currentTask: URLSessionDataTask?
  3.    
  4.     func loadProgressiveImage(from url: URL, placeholder: UIImage? = nil) {
  5.         // 取消之前的任务
  6.         currentTask?.cancel()
  7.         
  8.         // 设置占位图
  9.         image = placeholder
  10.         
  11.         // 创建请求
  12.         let request = URLRequest(url: url)
  13.         
  14.         // 创建数据任务
  15.         currentTask = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
  16.             guard let self = self, let data = data, error == nil else {
  17.                 return
  18.             }
  19.             
  20.             // 在后台队列处理图片
  21.             DispatchQueue.global(qos: .userInitiated).async {
  22.                 if let image = UIImage(data: data) {
  23.                     DispatchQueue.main.async {
  24.                         self.setImageWithTransition(image)
  25.                     }
  26.                 }
  27.             }
  28.         }
  29.         
  30.         // 启动任务
  31.         currentTask?.resume()
  32.     }
  33.    
  34.     func cancelLoad() {
  35.         currentTask?.cancel()
  36.         currentTask = nil
  37.     }
  38. }
  39. // 使用示例
  40. let progressiveImageView = ProgressiveImageView()
  41. if let url = URL(string: "https://example.com/image.jpg") {
  42.     progressiveImageView.loadProgressiveImage(from: url, placeholder: UIImage(named: "placeholder"))
  43. }
复制代码

实现图片预加载和缓存策略
  1. class SmartImageCache {
  2.     static let shared = SmartImageCache()
  3.     private var memoryCache = NSCache<NSString, UIImage>()
  4.     private var diskCache: URLCache
  5.     private let fileManager = FileManager.default
  6.     private let cachePath: URL
  7.    
  8.     private init() {
  9.         // 内存缓存设置
  10.         memoryCache.totalCostLimit = 100 * 1024 * 1024 // 100MB
  11.         memoryCache.countLimit = 50 // 最多缓存50张图片
  12.         
  13.         // 磁盘缓存设置
  14.         let diskCapacity = 500 * 1024 * 1024 // 500MB
  15.         diskCache = URLCache(memoryCapacity: 0, diskCapacity: diskCapacity, diskPath: nil)
  16.         
  17.         // 创建缓存目录
  18.         if let cachesURL = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first {
  19.             cachePath = cachesURL.appendingPathComponent("ImageCache")
  20.             
  21.             try? fileManager.createDirectory(at: cachePath, withIntermediateDirectories: true, attributes: nil)
  22.         } else {
  23.             cachePath = URL(fileURLWithPath: NSTemporaryDirectory())
  24.         }
  25.         
  26.         // 监听内存警告
  27.         NotificationCenter.default.addObserver(
  28.             self,
  29.             selector: #selector(clearMemoryCache),
  30.             name: UIApplication.didReceiveMemoryWarningNotification,
  31.             object: nil
  32.         )
  33.     }
  34.    
  35.     @objc private func clearMemoryCache() {
  36.         memoryCache.removeAllObjects()
  37.     }
  38.    
  39.     // 获取图片
  40.     func image(for url: URL, completion: @escaping (UIImage?) -> Void) {
  41.         let cacheKey = url.absoluteString as NSString
  42.         
  43.         // 检查内存缓存
  44.         if let cachedImage = memoryCache.object(forKey: cacheKey) {
  45.             completion(cachedImage)
  46.             return
  47.         }
  48.         
  49.         // 检查磁盘缓存
  50.         if let cachedResponse = diskCache.cachedResponse(for: URLRequest(url: url)),
  51.            let image = UIImage(data: cachedResponse.data) {
  52.             // 存入内存缓存
  53.             memoryCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
  54.             completion(image)
  55.             return
  56.         }
  57.         
  58.         // 从网络加载
  59.         downloadImage(from: url) { [weak self] image in
  60.             guard let self = self, let image = image else {
  61.                 completion(nil)
  62.                 return
  63.             }
  64.             
  65.             // 存入内存缓存
  66.             self.memoryCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
  67.             
  68.             // 存入磁盘缓存
  69.             if let data = image.jpegData(compressionQuality: 0.8) {
  70.                 let response = URLResponse(url: url, mimeType: "image/jpeg", expectedContentLength: data.count, textEncodingName: nil)
  71.                 let cachedResponse = CachedURLResponse(response: response, data: data)
  72.                 self.diskCache.storeCachedResponse(cachedResponse, for: URLRequest(url: url))
  73.             }
  74.             
  75.             completion(image)
  76.         }
  77.     }
  78.    
  79.     // 下载图片
  80.     private func downloadImage(from url: URL, completion: @escaping (UIImage?) -> Void) {
  81.         URLSession.shared.dataTask(with: url) { data, response, error in
  82.             guard let data = data, error == nil else {
  83.                 DispatchQueue.main.async {
  84.                     completion(nil)
  85.                 }
  86.                 return
  87.             }
  88.             
  89.             let image = UIImage(data: data)
  90.             DispatchQueue.main.async {
  91.                 completion(image)
  92.             }
  93.         }.resume()
  94.     }
  95.    
  96.     // 预加载图片
  97.     func preloadImages(from urls: [URL]) {
  98.         DispatchQueue.global(qos: .background).async {
  99.             for url in urls {
  100.                 _ = self.image(for: url) { _ in }
  101.             }
  102.         }
  103.     }
  104.    
  105.     // 清除所有缓存
  106.     func clearAllCache() {
  107.         clearMemoryCache()
  108.         diskCache.removeAllCachedResponses()
  109.         
  110.         try? fileManager.removeItem(at: cachePath)
  111.         try? fileManager.createDirectory(at: cachePath, withIntermediateDirectories: true, attributes: nil)
  112.     }
  113. }
  114. // 使用示例
  115. let smartCache = SmartImageCache.shared
  116. // 加载图片
  117. if let url = URL(string: "https://example.com/image.jpg") {
  118.     smartCache.image(for: url) { image in
  119.         if let image = image {
  120.             self.imageView.image = image
  121.         }
  122.     }
  123. }
  124. // 预加载图片
  125. let imageUrls = [
  126.     URL(string: "https://example.com/image1.jpg")!,
  127.     URL(string: "https://example.com/image2.jpg")!,
  128.     URL(string: "https://example.com/image3.jpg")!
  129. ]
  130. smartCache.preloadImages(from: imageUrls)
复制代码

实现图片懒加载和优先级加载
  1. enum ImageLoadPriority {
  2.     case high
  3.     case normal
  4.     case low
  5. }
  6. class PriorityImageLoader {
  7.     static let shared = PriorityImageLoader()
  8.     private var highPriorityQueue = OperationQueue()
  9.     private var normalPriorityQueue = OperationQueue()
  10.     private var lowPriorityQueue = OperationQueue()
  11.     private var imageCache = NSCache<NSString, UIImage>()
  12.    
  13.     private init() {
  14.         // 配置队列
  15.         highPriorityQueue.qualityOfService = .userInitiated
  16.         normalPriorityQueue.qualityOfService = .utility
  17.         lowPriorityQueue.qualityOfService = .background
  18.         
  19.         // 设置最大并发操作数
  20.         highPriorityQueue.maxConcurrentOperationCount = 2
  21.         normalPriorityQueue.maxConcurrentOperationCount = 3
  22.         lowPriorityQueue.maxConcurrentOperationCount = 5
  23.         
  24.         // 配置缓存
  25.         imageCache.totalCostLimit = 100 * 1024 * 1024 // 100MB
  26.         NotificationCenter.default.addObserver(
  27.             self,
  28.             selector: #selector(clearCache),
  29.             name: UIApplication.didReceiveMemoryWarningNotification,
  30.             object: nil
  31.         )
  32.     }
  33.    
  34.     @objc private func clearCache() {
  35.         imageCache.removeAllObjects()
  36.     }
  37.    
  38.     func loadImage(
  39.         from url: URL,
  40.         size: CGSize? = nil,
  41.         priority: ImageLoadPriority = .normal,
  42.         completion: @escaping (UIImage?) -> Void
  43.     ) {
  44.         let cacheKey = url.absoluteString as NSString
  45.         
  46.         // 检查缓存
  47.         if let cachedImage = imageCache.object(forKey: cacheKey) {
  48.             completion(cachedImage)
  49.             return
  50.         }
  51.         
  52.         // 创建操作
  53.         let operation = BlockOperation { [weak self] in
  54.             guard let self = self else { return }
  55.             
  56.             if let data = try? Data(contentsOf: url),
  57.                var image = UIImage(data: data) {
  58.                
  59.                 // 调整尺寸
  60.                 if let size = size {
  61.                     image = image.resized(to: size) ?? image
  62.                 }
  63.                
  64.                 // 缓存图片
  65.                 self.imageCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
  66.                
  67.                 DispatchQueue.main.async {
  68.                     completion(image)
  69.                 }
  70.             } else {
  71.                 DispatchQueue.main.async {
  72.                     completion(nil)
  73.                 }
  74.             }
  75.         }
  76.         
  77.         // 根据优先级添加到相应队列
  78.         switch priority {
  79.         case .high:
  80.             highPriorityQueue.addOperation(operation)
  81.         case .normal:
  82.             normalPriorityQueue.addOperation(operation)
  83.         case .low:
  84.             lowPriorityQueue.addOperation(operation)
  85.         }
  86.     }
  87.    
  88.     func cancelAllOperations() {
  89.         highPriorityQueue.cancelAllOperations()
  90.         normalPriorityQueue.cancelAllOperations()
  91.         lowPriorityQueue.cancelAllOperations()
  92.     }
  93. }
  94. // 使用示例
  95. let loader = PriorityImageLoader.shared
  96. // 加载高优先级图片
  97. if let url = URL(string: "https://example.com/important_image.jpg") {
  98.     loader.loadImage(from: url, priority: .high) { image in
  99.         if let image = image {
  100.             self.imageView.image = image
  101.         }
  102.     }
  103. }
  104. // 加载普通优先级图片
  105. if let url = URL(string: "https://example.com/normal_image.jpg") {
  106.     loader.loadImage(from: url, priority: .normal) { image in
  107.         if let image = image {
  108.             self.secondaryImageView.image = image
  109.         }
  110.     }
  111. }
  112. // 加载低优先级图片
  113. if let url = URL(string: "https://example.com/background_image.jpg") {
  114.     loader.loadImage(from: url, priority: .low) { image in
  115.         if let image = image {
  116.             self.backgroundImageView.image = image
  117.         }
  118.     }
  119. }
复制代码

总结

在Swift中有效管理UIImage的尺寸对于优化内存使用和提升应用性能至关重要。本文介绍了多种获取、调整和优化图片尺寸的方法,以及如何解决卡顿问题和打造流畅的用户体验。

关键要点包括:

1. 获取图片尺寸:了解如何从不同来源获取图片尺寸信息,包括资源文件、文件URL和网络。
2. 调整图片尺寸:掌握多种图片尺寸调整方法,包括简单调整、高质量调整、使用Core Image和使用vImage进行高性能调整。
3. 内存优化:通过计算图片内存占用、使用适当格式的图片、图像降采样和缓存策略来优化内存使用。
4. 解决卡顿问题:使用异步图片加载和处理、预加载和懒加载策略、占位图和渐进式加载以及图像解码优化来解决卡顿问题。
5. 打造流畅用户体验:实现平滑的图片过渡效果、图片淡入效果、渐进式图片加载、图片预加载和缓存策略以及图片懒加载和优先级加载。

获取图片尺寸:了解如何从不同来源获取图片尺寸信息,包括资源文件、文件URL和网络。

调整图片尺寸:掌握多种图片尺寸调整方法,包括简单调整、高质量调整、使用Core Image和使用vImage进行高性能调整。

内存优化:通过计算图片内存占用、使用适当格式的图片、图像降采样和缓存策略来优化内存使用。

解决卡顿问题:使用异步图片加载和处理、预加载和懒加载策略、占位图和渐进式加载以及图像解码优化来解决卡顿问题。

打造流畅用户体验:实现平滑的图片过渡效果、图片淡入效果、渐进式图片加载、图片预加载和缓存策略以及图片懒加载和优先级加载。

通过合理应用这些技术,你可以显著提升iOS应用的图片处理性能,减少内存占用,避免卡顿问题,为用户提供流畅的体验。记住,最佳实践往往是根据具体应用场景和需求来选择合适的策略,而不是盲目追求最复杂或最高性能的解决方案。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则