IOS Photo框架解析

PhotoKit类组成:

PHAdjustmentData,继承自NSObject,代表调整的数据.当用户编辑了一个asset(相册中的多媒体文件)、Photos框架会随着修改后的图像或视频数据生成一个adjustmentData对象。这个对象提供了程序默认的初始数据可以用来重现恢复这个编辑过的asset。

PHAsset,继承自PHObject。代表照片库中的一个资源,通过 PHAsset 可以获取和保存资源。

PHAssetChangeRequest,继承自NSObject,用于创建,删除,更改元数据或编辑照片资源内容的请求,以便在照片库更改块中使用。。

PHAssetCollectionChangeRequest,继承自NSObject,创建,删除或修改照片资产集合的请求,以便在照片库更改块中使用。。

PHAssetResourceCreationOptions,,继承自NSObject,影响从基础资源创建新Photos资产的一组选项。

PHAssetResource,继承自NSObject,与照片库中的照片,视频或Live Photo资产相关联的基础数据资源。

PHAssetResourceRequestOptions,继承自NSObject,影响您从资产资源管理器请求的基础资产数据交付的一组选项。

PHAssetResourceManager,继承自NSObject,提供访问与Photos资产关联的资源的基础数据存储的方法。

PHChange,继承自NSObject,“照片”库中发生的更改的说明。

PHCollection,继承自PHObject。

PHAssetCollection,继承自PHCollection。表示一个相册或者一个时刻,或者是一个「智能相册(系统提供的特定的一系列相册,例如:最近删除,视频列表,收藏等等,如下图所示)。    

PHCollectionListChangeRequest,继承自NSObject。

PHContentEditingInput ,继承自NSObject。

PHContentEditingOutput,继承自NSObject。

PHFetchOptions,继承自NSObject。获取资源时的参数,可以传 nil,即使用系统默认值。

PHFetchResult,继承自NSObject。表示一系列的资源结果集合,也可以是相册的集合,从    PHCollection 的类方法中获得。

PHImageRequestOptions,继承自NSObject。控制加载图片时的一系列参数。

PHLivePhoto,继承自NSObject,实时照片的可显示表示 - 包括拍摄前后瞬间的动作和声音的图片。

PHLivePhotoEditingContext,继承自NSObject。

PHObject,继承自NSObject,Photos模型对象(资产和集合)的抽象超类。

PHPhotoLibrary,继承自NSObject,一个共享对象,用于管理对用户照片库的访问和更改。

PHImageManager,继承自NSObject,提供用于检索或生成与Photos资源相关联的预览缩略图和全尺寸图像或视频数据的方法。



PhotoKit 定义了与系统的 Photos 应用内展现给用户的模型对象相一致的实体图表。这些照片实体都是轻量级的不可变对象。所有的 PhotoKit 对象都是继承自 PHObject 抽象基类,其公共接口只提供了一个 localIdentifier 属性。

PHAsset 表示用户照片库中一个单独的资源,用以提供资源的元数据

成组的资源叫做资源集合,用 PHAssetCollection 类表示。一个单独的资源集合可以是照片库中的一个相册或者一个时刻,或者是一个特殊的“智能相册”。这种智能相册包括所有的视频集合,最近添加的项目,用户收藏,所有连拍照片等等。PHAssetCollection 是 PHCollection 的子类。

PHCollectionList 表示一组的 PHCollections。因为它本身就是 PHCollection,所以集合列表可以包含其他集合列表,它们允许复杂的集合继承。实际上,我们可以在照片应用的时刻栏目中看到它:照片 --- 时刻 --- 精选 --- 年度,就是一个例子。

获取 (Fetch) 照片实体

获取 vs. 枚举

那些熟悉 AssetsLibrary 框架的开发者可能会记得 AssetsLibrary 可以用一些特定属性来找到需要的资源,其中一个必须枚举用户资源库来获得匹配的资源。不得不承认,这个 API 虽然提供了一些缩小搜索域的方法,但还是十分低效。

而与之形成鲜明对比,PhotoKit 实体的实例是通过获取得到的。那些熟悉 Core Data 的人,会觉得和 PhotoKit 在概念和描述都比较接近。

获取请求

获取操作是由上面描述的实体的类方法实现的。要使用哪个类/方法,取决于问题所在范围和你展示与遍历照片库的方式。所有获取方法的命名都是相似的:class func fetchXXX(..., options: PHFetchOptions) -> PHFetchResult 。options 参数给了我们一个对结果进行过滤和排序的途径,这和 NSFetchRequest 的 predicate 与 sortDescriptors 参数类似。

获取结果

你可能已经注意到了这些获取操作不是异步的。它们返回了一个 PHFetchResult 对象,可以用类似 NSArray的接口来访问结果内的集合。它会按需动态加载内容并且缓存最近请求的内容。这个行为和设置了 batchSize属性的 NSFetchRequest 返回的结果数组相似。对于 PHFetchResult 来说,没有办法用参数来指定这个行为,但是官网文档保证 “即使在处理大量的返回结果时,依然能够有最好的表现”。

就算满足请求的照片库内容发生了改变,获取方法所返回的 PHFetchResult 对象是不会自动更新。在后面的小节中,我们会介绍如何对返回的 PHFetchResult 对象的改变进行观察并处理更新内容。

就算满足请求的照片库内容发生了改变,获取方法所返回的 PHFetchResult 对象是不会自动更新。在后面的小节中,我们会介绍如何对返回的 PHFetchResult 对象的改变进行观察并处理更新内容。

临时集合 (Transient Collections)

你可能会发现你已经设计了一个可以操作资源集合的组件,并且你还希望它能够处理任意一组的资源。PhotoKit 通过临时资源集合,让我们可以轻松做到这点。

你可以通过 PHAsset 对象数组或是包含资源的 PHFetchResult 对象来创建临时资源集合。创建的操作在 PHAssetCollection 的 transientAssetCollectionWithAssets(...) 和 transientAssetCollectionWithFetchResult(...) 工厂方法内完成。这些方法创建出来的对象可以像其它的 PHAssetCollection 对象一样使用。尽管如此,这些集合不会被存储到用户照片库,自然也不会在照片应用中展示出来。

和资源集合相似,你可以用 PHCollectionList 中的 transientCollectionListWithXXX(...) 工厂方法来创建临时集合列表。

当你要合并两个获取请求时,你就会发现这个东西非常有用。

照片元数据

正如文章开头提到的,PhotoKit 提供了额外的关于用户资源的元数据,而这些数据在以前使用 ALAssetsLibrary 框架中是没有办法访问,或者很难访问到。

HDR 和全景照片

你可以使用照片资源的 mediaSubtypes 属性验证资源库中的图像在捕捉时是否开启了 HDR,拍摄时是否使用了相机应用的全景模式。

收藏和隐藏资源

要验证一个资源是否被用户标记为收藏或被隐藏,只要检查 PHAsset 实例的 favorite 和 hidden 属性即可。

连拍模式照片

对于一个资源,如果其 PHAsset 的 representsBurst 属性为 true,则表示这个资源是一系列连拍照片中的代表照片 (多张照片是在用户按住快门时拍摄的)。它还有一个属性是 burstIdentifier,如果想要获取连拍照片中的剩余的其他照片,可以通过将这个值传入 fetchAssetsWithBurstIdentifier(...) 方法来获取。

用户可以在连拍的照片中做标记;此外,系统也会自动用各种试探来标记用户可能会选择的潜在代表照片。这个元数据是可以通过 PHAsset 的 burstSelectionTypes 属性来访问。这个属性是用三个常量组成的位掩码:.UserPick 表示用户手动标记的资源,.AutoPick 表示用户可能标记的潜在资源,.None 表示没有标记的资源。

照片加载

在处理用户照片库的过去几年中,开发者创造了上百 (如果没有上千) 的小技巧来提高照片加载和展示的效率。这些技巧处理请求的派发和取消,图像大小的修改和裁剪,缓存等等。PhotoKit 提供了一个可以用更加便捷和现代的 API 做了所有这些操作的类:PHImageManager 。

请求图像

图像请求是通过 requestImageForAsset(...) 方法派发的。这个方法接受一个 PHAsset,可以设置返回图像的大小和图像的其它可选项 (通过 PHImageRequestOptions 参数对象设置),以及结果回调 (result handler)。这个方法的返回值可以用来在所请求的数据不再被需要时取消这个请求。

图像的尺寸和裁剪

奇怪的是,对返回图像的尺寸定义和裁剪的参数是分布在两个地方的。targetSize 和 contentMode 这俩参数会被直接传入 requestImageForAsset(...) 方法内。这个 content Mode 和 UIView 的 contentMode 参数类似,决定了照片应该以按比例缩放还是按比例填充的方式放到目标大小内。注意:如果不对照片大小进行修改或裁剪,那么方法参数是 PHImageManagerMaximumSize 和 PHImageContentMode.Default 。

此外,PHImageRequestOptions 还提供了一些方式来确定图像管理器该以怎样的方式来重新设置图像大小。resizeMode 属性可以设置为 .Exact (返回图像必须和目标大小相匹配),.Fast (比 .Exact 效率更高,但返回图像可能和目标大小不一样) 或者 .None。还有个值得一提的是,normalizedCroppingMode 属性让我们确定图像管理器应该如何裁剪图像。注意:如果设置了 normalizedcroppingMode 的值,那么 resizeMode 需要设置为 .Exact。

请求递送和进度

默认情况下,如果图像管理器决定要用最优策略,那么它会在将图像的高质量版本递送给你之前,先传递一个较低质量的版本。你可以通过 deliveryMode 属性来控制这个行为;上面所描述的默认行为的值为 .Opportunistic。如果你只想要高质量的图像,并且可以接受更长的加载时间,那么将属性设置为 .HighQualityFormat。如果你想要更快的加载速度,且可以牺牲一点图像质量,那么将属性设置为 .FastFormat。

你可以使用 PHImageRequestOptions 的 synchronous 属性,让 requestImage... 系列的方法变成同步操作。注意:当 synchronous 设为 true 时,deliveryMode 属性就会被忽略,并被当作 .HighQualityFormat 来处理。

在设置这些参数时,一定要考虑到你的一些用户有可能开启了 iCloud 照片库,这点非常重要。PhotoKit 的 API 不一定会对设备的照片和 iCloud 上照片进行区分 —— 它们都用同一个 requestImage 方法来加载。这意味着任意一个图像请求都有可能是一个通过蜂窝网络来进行的非常缓慢的网络请求。当你要用 .HighQualityFormat 或者做一个同步请求的时候,要牢记这个。注意:如果你想要确保请求不经过网络,可以将 networkAccessAllowed 设为 false 。

另一个和 iCloud 相关的属性是 progressHandler。你可以将它设为一个 PHAssetImageProgressHandler 的 block,当从 iCloud 下载照片时,它就会被图像管理器自动调用。

资源版本

PhotoKit 允许应用对照片进行无损的修改。对编辑过的照片,系统会对单独保存一份原始照片的拷贝和针对应用的调整数据。当用图像管理器获取资源时,你可以指定哪个版本的图像资源应该通过 result handler 被递送。这可以通过设置 version 属性来做到:.Current 会递送包含所有调整和修改的图像;.Unadjusted 会递送未被施加任何修改的图像;.Original 会递送原始的、最高质量的格式的图像 (例如 RAW 格式的数据。而当将属性设置为 .Unadjusted 时,会递送一个 JPEG)。

你可以在 Sam Davies 的文章《照片扩展》中,阅读框架中更多关于这方面的内容。

结果回调 (result handler)

结果回调是一个包含了一个 UIImage 变量和一个 info 字典作为参数的 block。根据参数和请求的选项,在请求的整个生命周期,它可以被图像管理器多次调用。

info 字典提供了关于当前请求状态的信息,比如:

图像是否必须从 iCloud 请求 (如果你初始化时将 networkAccessAllowed 设置成 false,那么就必须重新请求图像) —— PHImageResultIsInCloudKey 。

当前递送的 UIImage 是否是最终结果的低质量格式。当高质量图像正在下载时,这个可以让你给用户先展示一个预览图像 —— PHImageResultIsDegradedKey。

请求 ID (可以便捷的取消请求),以及请求是否已经被取消 —— PHI

mageResultRequestIDKey 和 PHImageCancelledKey。

如果没有图像提供给 result handler,字典内还会有一个错误信息 —— PHImageErrorKey。

这些值可以让你更新你的 UI 来告知用户,和上面讨论到的 progressHandler 一起,来表示出它们的加载状态。

缓存

当图像即将要展示在屏幕上时,比如当要在一组滚动的 collection 视图上展示大量的资源图像的缩略图时,预先将一些图像加载到内存中有时是非常有用的。PhotoKit 提供了一个 PHImageManager 的子类来处理这种特定的使用场景 —— PHImageCachingManager。

PHImageCachingManager 提供了一个关键方法 —— startCachingImagesForAssets(...)。你传入一个 PHAssets 类型的数组,一些请求参数,以及一些请求单个图像时即将用到的可选项。此外,还有一些方法可以让你通知缓存管理器来停止缓存特定资源列表,以及停止缓存所有图像。

allowsCachingHighQualityImages 属性可以让你指定图像管理器是否应该准备高质量图像。当缓存一个较短和不变的资源列表时,默认 true 的属性表现很好。但当要在 collection 视图上快速滑动时做缓存操作的话,最好将它设置成 false 。

注意:以我的经验,当用户正在有大量资源的 collection 视图上极其快速的滑动时,使用缓存管理器会损害滑动的表现效果。为这种特定的使用场景定制一个缓存行为是极其重要的。缓存窗口的大小,移动缓存窗口的时机和频率,allowsCachingHighQualityImages 的属性值 —— 这些参数都要在目标硬件上的真实照片库中仔细地调节,并测试表现效果。更进一步,你可以考虑在用户行为的基础上,动态的设置这些参数。

请求图像数据

最后,除了请求普通的 UIImage 之外,PHImageManager 提供了另一个方法可以返回 NSData 对象类型的资源数据,包括它的通用类型标识符,图像的展示方向。这个方法返回了这个资源的最多的信息。

沧海桑田,万物变迁

我们已经讨论了在用户照片库中请求资源的元数据,但是到目前为止,我们没提及如何更新我们获取的数据。照片库本质上是一大堆可变的状态,而第一节中提到的照片实体是不可变的对象。PhotoKit 可以让你接收你需要的、关于照片库变动的所有信息,以正确更新你的缓存状态。

观察变化

首先,你需要通过共享的 PHPhotoLibrary 对象,用 registerChangeObserver(...) 方法注册一个变化观察者 (这个观察者要遵从 PHPhotoLibraryChangeObserver 协议)。只要另一个应用或者用户在照片库中做的修改影响了你在变化前获取的任何资源或资源集合的话,变化观察者的 photoLibraryDidChange(...) 方法都会被调用。这个方法只有一个 PHChange 类型的参数,你可以用它来验证这些变化是否和你所感兴趣的获取对象有关联。

更新获取的结果

PHChange 提供了几个方法,让你可以通过传入任何你感兴趣的 PHObject 对象或 PHFetchResult 对象来追踪它们的变化。这几个方法是 changeDetailsForObject(...) 和 changeDetailsForFetchResult(...)。如果没有任何变化,这些方法会返回 nil,否则你可以借助 PHObjectChangeDetails 或 PHFetchResultChangeDetails 对象来观察这些变化。

PHObjectChangeDetails 提供了一个对最新的照片实体对象的引用,以及告诉你对象的图像数据是否曾变化过、对象是否曾被删除过的布尔值。

PHFetchResultChangeDetails 封装了施加在你之前通过获取所得到的 PHFetchResult 上的变化的信息。PHFetchResultChangeDetails 是为了尽可能简化 CollectionView 或 TableView 的更新操作而设计的。它的属性恰好映射到你在使用一个典型的 CollectionView 的 update handler 时所需要的信息。注意,若要正确的更新 UITableView/UICollectionView,你必须以正确顺序来处理变化,那就是:RICE —— removedIndexes,insertedIndexes,changedIndexes,enumerateMovesWithBlock (如果 hasMoves 为 true 的话)。另外,PHFetchResultChangeDetails 的 hasIncrementalChanges 属性可以被设置成 false,这意味着旧的获取结果应该全部被新的值代替。这种情况下,你应该调用 UITableView/UICollectionView 的 reloadData。

注意:没有必要以集中的方式处理变化。如果你应用中的多个组件需要处理照片实体,那么它们每个都要有自己的 PHPhotoLibraryChangeObserver 。接着组件就能靠自己查询 PHChange 对象,检测是否需要 (以及如何) 更新它们自己的状态。

随风而变

现在我们已经知道了如何观察用户和其他应用造成的变化,我们来尝试一下自己进行改变。

改变存在的对象

用 PhotoKit 在照片库做改变,说到底其实是先创建了一个链接到某个资源或者资源集合的变化请求对象,再设置请求对象的相关属性或调用合适的方法来描述你想要提交的变化。这个必须通过 performChanges(...) 方法,在提交到共享的 PHPhotoLibrary 的 block 内完成。注意:你需要准备好在 performChanges 方法的 completion block 里处理失败的情况。虽然处理的是能被多个参与者 (如你的应用,用户,其他应用,照片扩展等) 改变的状态,但这个方式能提供安全性,也相对易用。

想要修改资源,需要创建一个 PHAssetChangeRequest 。然后你就可以修改创建创建日期,资源位置,以及是否将隐藏资源,是否将资源看做用户收藏等。此外,你还可以从用户的库里删除资源。

类似地,若要修改资源集合或集合列表,需要创建一个 PHAssetCollectionChangeRequest 或 PHCollectionListChangeRequest 对象。然后你就可以修改集合标题,添加或删除集合成员,或者完全删除集合。

在你的变化提交到用户照片库前,系统会向用户展示一个明确的获取权限的警告框。

创建新对象

创建一个新资源的做法和修改已存在的资源类似。只要用 creationRequestForAssetFromXXX(...) 工厂方法,来创建变化请求,并传入资源图像数据 (或一个 URL)。如果你需要对新建的资源做额外的修改,你可以用创建变化请求的 placeholderForCreatedAsset 属性。它会返回一个可用的 placeholder 来代替“真实的” PHAsset 引用。

结论

我已经讨论了 PhotoKit 的基础知识,但仍然还有非常多的东西等着我们去发掘。你可以通过查看示例的各处的代码,观看 WWDC session 视频学习更多内容,发掘更深的知识,然后写一些自己的代码!PhotoKit 为 iOS 开发者开启了通往新世界可能性,在未来的数月或者数年里,我们肯定会看到更多基于这个基础构建的富有创造性的优秀产品。





参阅自:ObjC中国的照片框架。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容