概要

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

图片烘托机制(精简版)

了解 iOS 烘托优化的同学一定刷过 WWDC 18 的一个瑰宝视频 Image and Graphics Best Practices (现在好像现已下掉了,但是其他平台有,没有看过的同学必看!)。视频中说到,图片烘托的本钱或许要比许多人想的大的多。许多人会觉得图片烘托占用的内存和图片占用的磁盘巨细有关,其实彻底不是。图片烘托的真实内容占用其实是和图片的尺度有关。一个被压缩到极致的图片,通过解码之后,或许会是一个尺度超级大的图片,而图片的每个像素点都占用固定的内存,图片尺度越大则像素越多,从而占用的内存也就更大。

使用 preparingThumbnail 优化 iOS 图片展示

因此苹果主张,假如图片尺度远大于实际烘托的尺度的话,能够运用下采样的方法,只烘托一个小尺度的图片。这样就能极大优化内容占用。

会有同学认为图片的烘托尺度和 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 命中率也会下降。但优点是图片烘托压力变小了,带宽流量也少了。两种计划都是对烘托优化帮助很大的,我们能够借鉴一下。