聊聊ALAssetsLibrary 與 Photos

ALAssetsLibraryPhotos都是Apple提供訪問系統相冊資源的兩個標準庫,前者在iOS9以後已經被棄用,後者在iOS8上開始支持。可想而知,Photos庫提供了更全面更友好的接口。php

本文經過對比二者的用法來系統地學習一下「iOS訪問系統相冊資源」的知識點。重點會放在新的Photos庫。objective-c

首先來看看舊的ALAssetsLibrary庫。緩存

ALAssetsLibrary

An instance of ALAssetsLibrary provides access to the videos and photos that are under the control of the Photos application.app

ALAssetsLibrary相對來講是簡潔一些的,只有5個類:框架

  • ALAsset 表示一個照片/視頻資源實體
  • ALAssetRepresentation 表示一個資源的詳細信息
  • ALAssetsFilter 設置拉取條件(圖片?視頻?所有?)
  • ALAssetsGroup 表示一個相冊(照片組)
  • ALAssetsLibrary 對相冊的實際操做接口

建立一個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定義了不少資源的屬性,好比ALAssetPropertyLocationALAssetPropertyDurationALAssetPropertyOrientation等等,能夠經過- (id)valueForProperty:(NSString *)property方法來獲取值。

能夠經過thumbnailaspectRatioThumbnail屬性獲取資源的縮略圖。

雖然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來終止遍歷。

Photos

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, PHAssetCollectionChangeRequestPHCollectionListChangeRequest,都在PhotoLibraryperformChanges: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;複製代碼

獲取的結果PHAssetPHAssetCollectionPHCollectionList 都是輕量級的不可變對象,使用這些類時並無將其表明的圖像或視頻或是集合載入內存中,要使用其表明的圖像或視頻,須要經過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指定了圖片遞送進度的策略:

  • PHImageRequestOptionsDeliveryModeOpportunistic 默認行爲,同步獲取時返回一個結果;異步獲取時會返回多個結果,從低質量版本到高質量版本。
  • PHImageRequestOptionsDeliveryModeHighQualityFormat 只返回一次高質量的結果,能夠接受長時間的加載。在同步模式下,默認直接採用這個策略。
  • PHImageRequestOptionsDeliveryModeFastFormat 只返回一次結果,但質量稍微差一點點,是前面兩種策略的結合。

resizeMode指定了從新設置圖片大小的方式:

  • PHImageRequestOptionsResizeModeNone 不用從新設置
  • PHImageRequestOptionsResizeModeExact 返回圖像與targetSize同樣,若是指定了normalizedCropRect,則必須設置爲這個模式。
  • PHImageRequestOptionsResizeModeFast 已targetSize爲參考,優化解碼方式,效率更好一些,但結果可能比targetSize大。

normalizedCropRect原始圖片的單元座標上的裁剪矩形。只在 resizeMode 爲 Exact 時有效。

networkAccessAllowed是否下載iCloud上的照片。

progressHandler下載iCloud照片的進度處理器。

version針對編輯過的照片決定哪一個版本的圖像資源應該經過 result handler 被遞送。

  • PHImageRequestOptionsVersionCurrent 會遞送包含全部調整和修改的圖像。
  • PHImageRequestOptionsVersionUnadjusted 會遞送未被施加任何修改的圖像。
  • PHImageRequestOptionsVersionOriginal 會遞送原始的、最高質量的格式的圖像 (例如 RAW 格式的數據。而當將屬性設置爲 .Unadjusted 時,會遞送一個 JPEG

當你須要加載許多資源時,可使用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;複製代碼
相關文章
相關標籤/搜索