iOS图片框架PhotoKit-Swift版本(上)

一.PhotoKit 简介

PhotoKit 是一套比 AssetsLibrary 更完整也更高效的库,对资源的处理跟 AssetsLibrary 也有很大的不同。

首先简单介绍几个概念:
PHAsset: 代表照片库中的一个资源,跟 ALAsset 类似,通过 PHAsset 可以获取和保存资源
PHFetchOptions: 获取资源时的参数,可以传 nil,即使用系统默认值
PHFetchResult: 表示一系列的资源集合,也可以是相册的集合
PHAssetCollection: 表示一个相册或者一个时刻,或者是一个「智能相册(系统提供的特定的一系列相册,例如:最近删除,视频列表,收藏等等,如下图所示)
PHImageManager: 用于处理资源的加载,加载图片的过程带有缓存处理,可以通过传入一个 PHImageRequestOptions 控制资源的输出尺寸等规格
PHImageRequestOptions: 如上面所说,控制加载图片时的一系列参数
这里还有一个额外的概念PHCollectionList,表示一组?PHCollection,它本身也是一个?PHCollection,因此?PHCollection 作为一个集合,可以包含其他集合,这使到 PhotoKit 的组成比 ALAssetLibrary 要复杂一些。另外与 ALAssetLibrary 相似,一个 PHAsset 可以同时属于多个不同的 PHAssetCollection,最常见的例子就是刚刚拍摄的照片,至少同时属于“最近添加”、“相机胶卷”以及“照片 - 精选”这三个 PHAssetCollection。关于这几个概念的关系如下图:

iOS图片框架PhotoKit-Swift版本(上)_第1张图片

二. PhotoKit 的机制

1. 获取资源

在 ALAssetLibrary 中获取数据,无论是相册,还是资源,本质上都是使用枚举的方式,遍历照片库取得相应的数据,并且数据是从
ALAssetLibrary(照片库) - ALAssetGroup(相册)- ALAsset(资源)这一路径逐层获取,即使有直接从
ALAssetLibrary 这一层获取 ALAsset 的接口,本质上也是枚举 ALAssetLibrary
所得,并不是直接获取,这样的好处很明显,就是非常符合实际应用中资源的显示路径:照片库 - 相册 -
图片或视频,但由于采用枚举的方式获取资源,效率低而且不灵活。
而在 PhotoKit 中,则是采用“获取”的方式拉取资源,这些获取的手段,都是一系列形如 class func
fetchXXX(..., options: PHFetchOptions) -> PHFetchResult
的类方法,具体使用哪个类方法,则视乎需要获取的是相册、时刻还是资源,这类方法中的 option
充当了过滤器的作用,可以过滤相册的类型,日期,名称等,从而直接获取对应的资源而不需要枚举。例如在前文中列举个的几个小例子:

// 列出所有相册智能相册
let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum,    subtype: .albumRegular, options: nil)
// 列出所有用户创建的相册
let topLevelUserCollections = PHCollectionList.fetchTopLevelUserCollections(with: nil)
// 获取所有资源的集合,并按资源的创建时间排序
let options = PHFetchOptions()
options.sortDescriptors = [NSSortDescriptor.init(key: "creationDate", ascending: true)]
let assetsFetchResults = PHAsset.fetchAssets(with: options)

如前面提到过的那样,从PHAssetCollection
获取中获取到的可以是相册也可以是资源,但无论是哪种内容,都统一使用?PHFetchResult 对象封装起来,因此虽然
PHAssetCollection 获取到的结果可能是多样的,但通过?PHFetchResult 就可以使用统一的方法去处理这些内容(即遍历
PHFetchResult)。例如扩展上面的例子:

// 列出所有相册智能相册
    let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options: nil)
    for i in 0..

2. 获取图像的方式与坑点

 经过了上面的步骤,已经可以了解到如何在 PhotoKit 中获取到代表资源的 PHAsset 了,但与 ALAssetLibrary 中从 ALAsset 中直接获取图像的方式不同,PhotoKit 无法直接从 PHAsset 的实例中获取图像,而是引入了一个管理器?PHImageManager 获取图像。PHImageManager 是通过请求的方式拉取图像,并可以控制请求得到的图像的尺寸、剪裁方式、质量,缓存以及请求本身的管理(发出请求、取消请求)等。而请求图像的方法是 ?PHImageManager 的一个实例方法:


open func requestImage(for asset: PHAsset, targetSize: CGSize, contentMode: PHImageContentMode, options: PHImageRequestOptions?, resultHandler: @escaping (UIImage?, [AnyHashable : Any]?) -> Swift.Void) -> PHImageRequestID

这个方法中的参数坑点不少,下面逐个参数列举一下其作用及坑点:

1.asset,图像对应的 PHAsset。
2.targetSize,需要获取的图像的尺寸,如果输入的尺寸大于资源原图的尺寸,则只返回原图。需要注意在 PHImageManager 中,所有的尺寸都是用 Pixel 作为单位(Note that all sizes are in pixels),因此这里想要获得正确大小的图像,需要把输入的尺寸转换为 Pixel。如果需要返回原图尺寸,可以传入 PhotoKit 中预先定义好的常量?PHImageManagerMaximumSize,表示返回可选范围内的最大的尺寸,即原图尺寸。
3.contentMode,图像的剪裁方式,与?UIView 的 contentMode 参数相似,控制照片应该以按比例缩放还是按比例填充的方式放到最终展示的容器内。注意如果 targetSize 传入PHImageManagerMaximumSize,则 contentMode 无论传入什么值都会被视为PHImageContentModeDefault。
4.options,一个?PHImageRequestOptions 的实例,可以控制的内容相当丰富,包括图像的质量、版本,也会有参数控制图像的剪裁,下面再展开说明。
5.resultHandler,请求结束后被调用的 block,返回一个包含资源对于图像的 UIImage 和包含图像信息的一个 NSDictionary,在整个请求的周期中,这个 block 可能会被多次调用,关于这点连同 options 参数在下面展开说明。

3.获取图像的优化

PHImageManager 提供了一个子类?PHImageCachingManager 用于处理图像的缓存,但是这个子类并不只是图像本身的缓存,而是更加实用——处理图像的整个加载过程的缓存。例如要在一个?collectionView 上展示图像列表这类大量的资源图像的缩略图时,可以利用 PHImageCachingManager?预先将一些图像加载到内存中,这对优化 collectionView 滚动时的表现很有帮助。然而,这只是官方说法,实际上由于加载图像的过程并不确定,每个业务加载图像的实际需求都可能不一样,因此PHCachingImageManager 也采用比较松散的方法去控制这些缓存,其中的关键方法:

open func startCachingImages(for assets: [PHAsset], targetSize: CGSize, contentMode: PHImageContentMode, options: PHImageRequestOptions?)

需要传入一组 PHAsset,以及 targetSize,contentMode,以及一个?PHImageRequestOptions,如上面所述,这些参数之间的有着互相影响的作用,因此实际上不同的场景对于每个参数要求都不一样,而这些参数的最佳取值也只能通过实际在场景中测试所得。因此,比起使用?PHImageCachingManager,我总结了一些更为简易可行的缓存方法:

  1. 获取图片时尽量获取预览图,不要直接显示原件,建议获取与设备屏幕同样大小的图像即可,实际上系统相册预览大图时使用的也是预览图,这也是系统相册加载速度快的原因。
    2.获取图片使用异步请求,如上面所述,当请求为异步时返回图像的 block 会被多次调用,先返回低清图,再返回高清图,这样一来可以大大减少 UI 的等待时间。
    3.获取到高清图后可以缓存下来,简单地使用变量缓存即可,尽量在获取到高清图后避免再次发起请求获取图像。因为即使图像原件已经下载下来,重新请求高清图时因为图片的尺寸比较大,因此系统生成图像和剪裁图像也会花费一些时间。
    4.预先加载图像,如像预览大图这类情景中,用户同时只会看到一张大图,因此在观看某一张图片时,预先请求其邻近两张图片,对于加快 UI 的响应很有帮助。

经过实际测试,如果请求的是缩略图(即尺寸小的图像),那么即使请求的图像很多,仍不会产生任何不流畅的表现,但如果请求的是高清大图,那么即使只是同时请求几张图都会产生不流畅的状况。如上面提到过的那样,这些的状况的出现很可能是请求大图时由图片元数据产生图像,以及剪裁图像的过程耗时较多。所以按实际表现来看,即使 PhotoKit 有自己的缓存策略,仍然很难避免这部分耗时。因此上面几点优化获取图像的策略重点也是放在减少图像大小,异步请求以及做缓存几个方面。

你可能感兴趣的:(iOS图片框架PhotoKit-Swift版本(上))