ALAssetsLibrary
和Photos
都是Apple提供访问系统相册资源的两个标准库,前者在iOS9以后已经被弃用,后者在iOS8上开始支持。可想而知,Photos
库提供了更全面更友好的接口。php
本文经过对比二者的用法来系统地学习一下“iOS访问系统相册资源”的知识点。重点会放在新的Photos
库。objective-c
首先来看看旧的ALAssetsLibrary
库。缓存
An instance of ALAssetsLibrary provides access to the videos and photos that are under the control of the Photos application.app
ALAssetsLibrary
相对来讲是简洁一些的,只有5个类:框架
建立一个ALAssetsLibrary
:异步
ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];复制代码
这里要注意:“AssetsLibrary 实例须要强引用” ,引用官方文档:ide
The lifetimes of objects you get back from a library instance are tied to the lifetime of the library instance.post
能够以下测试:学习
- (void)viewDidLoad {
[super viewDidLoad];
_photos = [NSMutableArray new];
ALAssetsLibrary *al = [[ALAssetsLibrary alloc] init];
[al enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group) {
[group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
[_photos addObject:result];
}
}];
*stop = YES;
}
} failureBlock:^(NSError *error) {
}];
//因为ALAssetsLibrary的全部操做都是异步的,这里要在主线程
//延迟访问_photos
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self processResAssets];
});
}
- (void)processResAssets {
for (ALAsset *asset in _photos) {
CGImageRef *imgRef = asset.thumbnail;
UIImage *img = [UIImage imageWithCGImage:imgRef];
NSLog(@"%@",img);
}
}复制代码
上面代码中的ALAssetsLibrary
实例是局部变量,在processResAssets
方法中访问_photos时,因为_photos存储的只是表明资源文件的指针信息,真正保存资源文件的AssetsLibrary已经被释放了,因此取出来的资源都是nil的。测试
因此咱们要确保ALAssetsLibrary实例是strong类型的属性或者是单例的。
ALAssetsLibrary
类定义了一些Block,其中
typedef void (^ALAssetsLibraryGroupsEnumerationResultsBlock)(ALAssetsGroup *group, BOOL *stop)复制代码
能够设置stop为true来终止block, 而不能像普通的block同样经过return来终止,其余相似的block都是这个用法。
用ALAssetLibrary
还有一个要注意的写入优先原则,就是说在利用 AssetsLibrary 读取资源的过程当中,有任何其它的进程(不必定是同一个 App)在保存资源时,就会收到 ALAssetsLibraryChangedNotification,让用户自行中断读取操做。最多见的就是读取 fullResolutionImage 时,有进程在写入,因为读取 fullResolutionImage 耗时较长,很容易就会 exception。
ALAssetsLibrary
提供的接口主要是两大类:
增
- (void)writeImageToSavedPhotosAlbum:(CGImageRef)imageRef orientation:(ALAssetOrientation)orientation completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock
- (void)writeImageToSavedPhotosAlbum:(CGImageRef)imageRef metadata:(NSDictionary *)metadata completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock
- (void)writeImageDataToSavedPhotosAlbum:(NSData *)imageData metadata:(NSDictionary *)metadata completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock
- (void)writeVideoAtPathToSavedPhotosAlbum:(NSURL *)videoPathURL completionBlock:(ALAssetsLibraryWriteVideoCompletionBlock)completionBlock复制代码
查
- (void)enumerateGroupsWithTypes:(ALAssetsGroupType)types usingBlock:(ALAssetsLibraryGroupsEnumerationResultsBlock)enumerationBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
- (void)assetForURL:(NSURL *)assetURL resultBlock:(ALAssetsLibraryAssetForURLResultBlock)resultBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
- (void)groupForURL:(NSURL *)groupURL resultBlock:(ALAssetsLibraryGroupResultBlock)resultBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock复制代码
能够看到,ALAssetsLibrary
并无提供删和改的接口。
ALAssetsLibrary在第一次增、查的时候会提示用户打开访问相册的权限,这帮开发者省略了本身写权限判断的逻辑。固然,前提是在项目的info.plist中定义了Privacy - Photo Library Usage Description
这个key,不然会crash。
ALAsset定义了不少资源的属性,好比ALAssetPropertyLocation
、ALAssetPropertyDuration
、ALAssetPropertyOrientation
等等,能够经过- (id)valueForProperty:(NSString *)property
方法来获取值。
能够经过thumbnail
和aspectRatioThumbnail
属性获取资源的缩略图。
虽然ALAssetsLibrary
没有直接提供更新资源的接口,可是ALAsset
本身提供了。ALAsset
不只能够更新资源数据,还能够选择直接覆盖当前资源仍是生成一个新的资源。更新的前提是editable属性为true。
//把当前ALAsset更新以后的数据写到新的ALAsset对象中去
- (void)writeModifiedImageDataToSavedPhotosAlbum:(NSData *)imageData metadata:(NSDictionary *)metadata completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock
- (void)writeModifiedVideoAtPathToSavedPhotosAlbum:(NSURL *)videoPathURL completionBlock:(ALAssetsLibraryWriteVideoCompletionBlock)completionBlock
//直接将更新后的资源数据覆盖原来的资源上,AssetURL不变
- (void)setImageData:(NSData *)imageData metadata:(NSDictionary *)metadata completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock
- (void)setVideoAtPath:(NSURL *)videoPathURL completionBlock:(ALAssetsLibraryWriteVideoCompletionBlock)completionBlock复制代码
若是资源被更新了还想看原来的资源怎么办,Apple已经帮咱们想到这个问题了,originalAsset就是原始的资源。遗憾的是,若是咱们更新了资源却没有存储,那就没办法找到原来的资源了。
ALAssetRepresentation是对 ALAsset 的封装,能够更方便地获取 ALAsset 中的资源信息,好比url、filename、scale等等。每一个 ALAsset 都有至少有一个 ALAssetRepresentation 对象,能够经过 defaultRepresentation 获取。而例如使用系统相机应用拍摄的 RAW + JPEG 照片,则会有两个 ALAssetRepresentation,一个封装了照片的 RAW 信息,另外一个则封装了照片的 JPEG 信息
其中fullScreenImage
比较经常使用,就是返回一个屏幕大小的缩略图,比thumbnail大一些,但仍然是分辨率比较低的图片。可是这个颇有用,由于它既知足了预览的清晰度要求,也加快了加载速度。
与之对应的是fullResolutionImage
,它表示原分辨率的图片,固然是最清晰的版本,也是最大的,因此加载速度很慢。不多用到。
ALAssetsGroup就是相册,其顺序就是系统相册看到的顺序。
手机的每一个相册都有一个预览图,是由posterImage
属性指定的。
一样地,ALAssetsGroup
也提供了增、查的接口。
//增
// Returns YES if the asset was added successfully. Returns NO if the group is not editable, or if the asset was not able to be added to the group.
- (BOOL)addAsset:(ALAsset *)asset;
//查
- (void)enumerateAssetsUsingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock
- (void)enumerateAssetsWithOptions:(NSEnumerationOptions)options usingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock
- (void)enumerateAssetsAtIndexes:(NSIndexSet *)indexSet options:(NSEnumerationOptions)options usingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock复制代码
其中,enumerateAssetsWithOptions:usingBlock:
能够经过指定NSEnumerationReverse
选项来倒序遍历相册。
在ALAssetsGroupEnumerationResultsBlock
处理资源,同上,能够指定stop=true来终止遍历。
The shared PHPhotoLibrary object represents the entire set of assets and collections managed by the Photos app, including both assets stored on the local device and (if enabled) those stored in iCloud Photos
官方建议,iOS8以后开始用Photos
库来替代ALAssetLibrary
库。Photos
提供了额外的关于用户资源的元数据,而这些数据在之前使用 ALAssetsLibrary 框架中是没有办法访问,或者很难访问到。这点能够从PhotosTypes.h
中看出来,好比能够验证资源库中的图像在捕捉时是否开启了 HDR;拍摄时是否使用了相机应用的全景模式;是否被用户标记为收藏或被隐藏等等信息。
Photos
淡化照片库中 URL 的概念,改之使用一个标志符来惟一表明一个资源,即localIdentifier。其带来的最大好处是PHObject类实现了 NSCopying 协议,能够直接使用localIdentifier属性对PHObject及其子类对象进行对比是否同一个对象。
Photos
提供了更全面的接口,涵盖了增、删、改、查的全部方面。能够参考官方文档。这些操做都是基于相应的变动请求类PHAssetChangeRequest
, PHAssetCollectionChangeRequest
和PHCollectionListChangeRequest
,都在PhotoLibrary
的performChanges:completionHandler:
或者performChangesAndWait:error:
的changeBlock
中执行。
增
每一个change request
的类中都提供了一个新增资源的方法:
//PHAssetChangeRequest
+ (instancetype)creationRequestForAssetFromImage:(UIImage *)image;
+ (nullable instancetype)creationRequestForAssetFromImageAtFileURL:(NSURL *)fileURL;
+ (nullable instancetype)creationRequestForAssetFromVideoAtFileURL:(NSURL *)fileURL;
//PHAssetCollectionChangeRequest
+ (instancetype)creationRequestForAssetCollectionWithTitle:(NSString *)title;
//PHCollectionListChangeRequest
+ (instancetype)creationRequestForCollectionListWithTitle:(NSString *)title;复制代码
删
每一个change request
的类中都提供了一个删除资源的方法:
//PHAssetChangeRequest
+ (void)deleteAssets:(id<NSFastEnumeration>)assets;
//PHAssetCollectionChangeRequest
+ (void)deleteAssetCollections:(id<NSFastEnumeration>)assetCollections;
//PHCollectionListChangeRequest
+ (void)deleteCollectionLists:(id<NSFastEnumeration>)collectionLists;复制代码
改
建立change request
以后,可使用属性或者实例化方法来修改它表明的asset或者collection的相应特性。好比changeRequestForAsset:
方法能够根据目标asset建立一个 change request
,而后能够修改favorite属性.
//PHAssetChangeRequest
+ (instancetype)changeRequestForAsset:(PHAsset *)asset;
//PHAssetCollectionChangeRequest
+ (nullable instancetype)changeRequestForAssetCollection:(PHAssetCollection *)assetCollection;
+ (nullable instancetype)changeRequestForAssetCollection:(PHAssetCollection *)assetCollection assets:(PHFetchResult<PHAsset *> *)assets;
//PHCollectionListChangeRequest
+ (nullable instancetype)changeRequestForCollectionList:(PHCollectionList *)collectionList;
+ (nullable instancetype)changeRequestForCollectionList:(PHCollectionList *)collectionList childCollections:(PHFetchResult<__kindof PHCollection *> *)childCollections;复制代码
官方文档给了一个建立asset添加到album的例子:
- (void)addNewAssetWithImage:(UIImage *)image toAlbum:(PHAssetCollection *)album {
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetChangeRequest *createAssetRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
PHAssetCollectionChangeRequest *albumChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:album];
PHObjectPlaceholder *assetPlaceholder = [createAssetRequest placeholderForCreatedAsset]; [albumChangeRequest addAssets:@[ assetPlaceholder ]];
} completionHandler:^(BOOL success, NSError *error) {
NSLog(@"Finished adding asset. %@", (success ? @"Success" : error));
}];
}复制代码
每一个change request
都有一个PHObjectPlaceholder类型的属性,其做用是给新建立的asset或者collection占位,能够在change block
完成以后直接获取到新建立的资源。你也能够直接在change block
里直接添加到change request
中去。
每次在调用performChanges:completionHandler:
或者 performChangesAndWait:error:
方法时,Photos均可能尝试提醒用户访问相册权限。
你能够在一个change block
合并提交多个change request
。
查Photos
中有两种资源可供获取:PHAsset 和 PHCollection。PHCollection有PHAssetCollection和PHCollectionList两个子类。获取资源的过程相似于Core Data:
/*PHAsset*/
+ (PHFetchResult<PHAsset *> *)fetchAssetsInAssetCollection:(PHAssetCollection *)assetCollection options:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithLocalIdentifiers:(NSArray<NSString *> *)identifiers options:(nullable PHFetchOptions *)options; // includes hidden assets by default
+ (nullable PHFetchResult<PHAsset *> *)fetchKeyAssetsInAssetCollection:(PHAssetCollection *)assetCollection options:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithBurstIdentifier:(NSString *)burstIdentifier options:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithOptions:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithMediaType:(PHAssetMediaType)mediaType options:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithALAssetURLs:(NSArray<NSURL *> *)assetURLs options:(nullable PHFetchOptions *)options
/*PHAssetCollection*/
+ (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsWithLocalIdentifiers:(NSArray<NSString *> *)identifiers options:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsWithType:(PHAssetCollectionType)type subtype:(PHAssetCollectionSubtype)subtype options:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsContainingAsset:(PHAsset *)asset withType:(PHAssetCollectionType)type options:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsWithALAssetGroupURLs:(NSArray<NSURL *> *)assetGroupURLs options:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHAssetCollection *> *)fetchMomentsInMomentList:(PHCollectionList *)momentList options:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHAssetCollection *> *)fetchMomentsWithOptions:(nullable PHFetchOptions *)options;
/*PHCollectionList*/
+ (PHFetchResult<PHCollectionList *> *)fetchCollectionListsContainingCollection:(PHCollection *)collection options:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHCollectionList *> *)fetchCollectionListsWithLocalIdentifiers:(NSArray<NSString *> *)identifiers options:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHCollectionList *> *)fetchCollectionListsWithType:(PHCollectionListType)collectionListType subtype:(PHCollectionListSubtype)subtype options:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHCollectionList *> *)fetchMomentListsWithSubtype:(PHCollectionListSubtype)momentListSubtype containingMoment:(PHAssetCollection *)moment options:(nullable PHFetchOptions *)options;
+ (PHFetchResult<PHCollectionList *> *)fetchMomentListsWithSubtype:(PHCollectionListSubtype)momentListSubtype options:(nullable PHFetchOptions *)options;复制代码
获取的结果PHAsset
、PHAssetCollection
和PHCollectionList
都是轻量级的不可变对象,使用这些类时并无将其表明的图像或视频或是集合载入内存中,要使用其表明的图像或视频,须要经过PHImageManager
类来请求。
#pragma mark - Image
- (PHImageRequestID)requestImageForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler;
- (PHImageRequestID)requestImageDataForAsset:(PHAsset *)asset options:(nullable PHImageRequestOptions *)options resultHandler:(void(^)(NSData *__nullable imageData, NSString *__nullable dataUTI, UIImageOrientation orientation, NSDictionary *__nullable info))resultHandler;
#pragma mark - Live Photo
- (PHImageRequestID)requestLivePhotoForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHLivePhotoRequestOptions *)options resultHandler:(void (^)(PHLivePhoto *__nullable livePhoto, NSDictionary *__nullable info))resultHandler PHOTOS_AVAILABLE_IOS_TVOS(9_1, 10_0);
#pragma mark - Video
- (PHImageRequestID)requestPlayerItemForVideo:(PHAsset *)asset options:(nullable PHVideoRequestOptions *)options resultHandler:(void (^)(AVPlayerItem *__nullable playerItem, NSDictionary *__nullable info))resultHandler;
- (PHImageRequestID)requestExportSessionForVideo:(PHAsset *)asset options:(nullable PHVideoRequestOptions *)options exportPreset:(NSString *)exportPreset resultHandler:(void (^)(AVAssetExportSession *__nullable exportSession, NSDictionary *__nullable info))resultHandler;
- (PHImageRequestID)requestAVAssetForVideo:(PHAsset *)asset options:(nullable PHVideoRequestOptions *)options resultHandler:(void (^)(AVAsset *__nullable asset, AVAudioMix *__nullable audioMix, NSDictionary *__nullable info))resultHandler;复制代码
iOS11的系统相册支持了GIF,这个时候或取GIF就要用requestImageDataForAsset
了,不然是一张静图。
targetSize
指定了图片的目标大小,可是结果不必定就是这个大小,还要以来后面options的设置; contentMode
相似于UIView的contentMode属性,决定了照片应该以按比例缩放仍是按比例填充的方式放到目标大小内。若是不对照片大小进行修改或裁剪,那么方法参数是 PHImageManagerMaximumSize 和 PHImageContentMode.Default。
PHImageRequestOptions提供了设置图片的其余一些属性。
deliveryMode
指定了图片递送进度的策略:
resizeMode
指定了从新设置图片大小的方式:
normalizedCropRect
原始图片的单元坐标上的裁剪矩形。只在 resizeMode 为 Exact 时有效。
networkAccessAllowed
是否下载iCloud上的照片。
progressHandler
下载iCloud照片的进度处理器。
version
针对编辑过的照片决定哪一个版本的图像资源应该经过 result handler 被递送。
当你须要加载许多资源时,可使用PHCachingImageManager。好比当要在一组滚动的 collection 视图上展现大量的资源图像的缩略图时,预先将一些图像加载到内存中有时是很是有用的。
在缓存的时候,只是照片资源被缓存,此时尚未裁剪和大小设置;
若是同时对一个asset有多个不一样options或targetSize的缓存请求时,采起FIFO的原则。
- (void)startCachingImagesForAssets:(NSArray<PHAsset *> *)assets targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options;
- (void)stopCachingImagesForAssets:(NSArray<PHAsset *> *)assets targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options;
- (void)stopCachingImagesForAllAssets;复制代码