欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

使用准备缩略图优化 iOS 图像呈现

最编程 2024-04-25 18:31:46
...

概要

今天是 WWDC 2023 的第二天,我在疯狂刷讲座的过程中突然瞥到了一个之前没用见的 API:preparing​Thumbnail(of:),于是稍微研究了一下,感觉还是非常香的,于是记下来分享一下。

图片渲染机制(精简版)

了解 iOS 渲染优化的同学一定刷过 WWDC 18 的一个宝藏视频 Image and Graphics Best Practices (目前似乎已经下掉了,但是其他平台有,没有看过的同学必看!)。视频中提到,图片渲染的成本可能要比很多人想的大的多。很多人会觉得图片渲染占用的内存和图片占用的磁盘大小有关,其实完全不是。图片渲染的真实内容占用其实是和图片的尺寸有关。一个被压缩到极致的图片,经过解码之后,可能会是一个尺寸超级大的图片,而图片的每个像素点都占用固定的内存,图片尺寸越大则像素越多,进而占用的内存也就更大。

image.png

因此苹果建议,如果图片尺寸远大于实际渲染的尺寸的话,可以使用下采样的方式,只渲染一个小尺寸的图片。这样就能极大优化内容占用。

会有同学认为图片的渲染尺寸和 UIImageView 的尺寸一样,其实不是。虽然 UIImageView 限定了图片在屏幕上的渲染尺寸,但图片在内存中是按完整尺寸储存的。

优化图片渲染

读到这边很多同学可能就准备上手使用 UIGraphicsImageRendererUIImage 压缩到小尺寸了。但其实这种方式成本非常高,因为把图片读到 UIImage 的过程中其实已经是将完整图片尺寸解码出来了,经过 UIGraphicsImageRenderer 转换之后虽然能得到一个较小的图片,但是中间额外的解码压缩过程效率很低,还会造成内存波动。

苹果推荐使用 ImageIO 提供的更底层的 API 来渲染图片。这个 API 会直接从源文件中解码出指定尺寸的图片数据,因此效率更高。一句话总结就是速度更快、内存占用更小。

import UIKit
import ImageIO
struct ImageIOConverter{
    static func resize(url: URL)-> UIImage{ 
        guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil) else { 
            fatalError("Can not get imageSource") 
        } 
        let options: [NSString: Any] = [ kCGImageSourceThumbnailMaxPixelSize: 300, kCGImageSourceCreateThumbnailFromImageAlways: true ] 
        guard let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) else { 
            fatalError("Can not get scaledImage") 
        } 
        return UIImage(cgImage: scaledImage) 
    } 
}

不过 ImageIO 毕竟是偏底层的框架,代码的复杂度也很高。于是 Apple 在 WWDC21 推出了 preparingThumbnail 方法,可以方便快捷的生成指定大小的图片,建议能用上的地方都用上。

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as? ItemCell else { 
        fatalError("Unexpected type for cell. Check configuration.") 
    } 
    let item = items[indexPath.item] cell.nameLabel?.text = item.name 
    let thumbnail = item.image.preparingThumbnail(of: thumbnailSize) 
    cell.thumbnailImageView?.image = thumbnail return cell 
}

这个 API 还贴心的准备了三个版本:

  • 同步版:preparingThumbnail(of:)
  • 异步版:prepareThumbnail(of:completionHandler:)
  • 协程版:byPreparingThumbnail(ofSize:)

但坏消息是只支持 iOS 15.0+ 。 另外 SwiftUI 这边也没有发现类似的 API ,看来只能用 UIImage 曲线救国了。

总结

当我发现这么重要的 API 竟然是 21 年发布的还是挺吃惊的,隔壁 Flutter 很早就上了 ResizeImage ,做的也是一样的事情。

另外现在很多公司为了优化图片渲染都会考虑在云端对图片做裁剪,但一来服务器成本变高,二来 CDN 命中率也会下降。但好处是图片渲染压力变小了,带宽流量也少了。两种方案都是对渲染优化帮助很大的,大家可以借鉴一下。