你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS PhotoKit框架 詳解

iOS PhotoKit框架 詳解

編輯:IOS開發綜合

一. 概況
本文側重介紹在前文中簡單介紹過的 PhotoKit 及其與 ALAssetLibrary 的差異,以及如何基於 PhotoKit 與 AlAssetLibrary 封裝出通用的方法。
這裡引用一下前文中對 PhotoKit 基本構成的介紹:
PHAsset: 代表照片庫中的一個資源,跟 ALAsset 類似,通過 PHAsset 可以獲取和保存資源
PHFetchOptions: 獲取資源時的參數,可以傳 nil,即使用系統默認值
PHAssetCollection: PHCollection 的子類,表示一個相冊或者一個時刻,或者是一個「智能相冊(系統提供的特定的一系列相冊,例如:最近刪除,視頻列表,收藏等等,如下圖所示)
PHFetchResult: 表示一系列的資源結果集合,也可以是相冊的集合,從 PHCollection 的類方法中獲得
PHImageManager: 用於處理資源的加載,加載圖片的過程帶有緩存處理,可以通過傳入一個 PHImageRequestOptions 控制資源的輸出尺寸等規格
PHImageRequestOptions: 如上面所說,控制加載圖片時的一系列參數

這裡還有一個額外的概念 PHCollectionList,表示一組 PHCollection,它本身也是一個 PHCollection,因此 PHCollection 作為一個集合,可以包含其他集合,這使到 PhotoKit 的組成比 ALAssetLibrary 要復雜一些。另外與 ALAssetLibrary 相似,一個 PHAsset 可以同時屬於多個不同的 PHAssetCollection,最常見的例子就是剛剛拍攝的照片,至少同時屬於“最近添加”、“相機膠卷”以及“照片 - 精選”這三個 PHAssetCollection。關於這幾個概念的關系如下圖:

\

 

 

二. PhotoKit 的機制

獲取資源
在 ALAssetLibrary 中獲取數據,無論是相冊,還是資源,本質上都是使用枚舉的方式,遍歷照片庫取得相應的數據,並且數據是從 ALAssetLibrary(照片庫) - ALAssetGroup(相冊)- ALAsset(資源)這一路徑逐層獲取,即使有直接從 ALAssetLibrary 這一層獲取 ALAsset 的接口,本質上也是枚舉 ALAssetLibrary 所得,並不是直接獲取,這樣的好處很明顯,就是非常符合實際應用中資源的顯示路徑:照片庫 - 相冊 - 圖片或視頻,但由於采用枚舉的方式獲取資源,效率低而且不靈活。
而在 PhotoKit 中,則是采用“獲取”的方式拉取資源,這些獲取的手段,都是一系列形如 class func fetchXXX(..., options: PHFetchOptions) -> PHFetchResult 的類方法,具體使用哪個類方法,則視乎需要獲取的是相冊、時刻還是資源,這類方法中的 option 充當了過濾器的作用,可以過濾相冊的類型,日期,名稱等,從而直接獲取對應的資源而不需要枚舉。例如在前文中列舉個的幾個小例子:
// 列出所有相冊智能相冊
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];

// 列出所有用戶創建的相冊
PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];

// 獲取所有資源的集合,並按資源的創建時間排序
PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];

如前面提到過的那樣,從 PHAssetCollection 獲取中獲取到的可以是相冊也可以是資源,但無論是哪種內容,都統一使用 PHFetchResult 對象封裝起來,因此雖然 PHAssetCollection 獲取到的結果可能是多樣的,但通過 PHFetchResult 就可以使用統一的方法去處理這些內容(即遍歷 PHFetchResult)。例如擴展上面的例子:



// 列出所有相冊智能相冊
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
// 這時 smartAlbums 中保存的應該是各個智能相冊對應的 PHAssetCollection
for (NSInteger i = 0; i < fetchResult.count; i++) {
    // 獲取一個相冊(PHAssetCollection)
    PHCollection *collection = fetchResult[i];
    if ([collection isKindOfClass:[PHAssetCollection class]]) {
        PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
        // 從每一個智能相冊中獲取到的 PHFetchResult 中包含的才是真正的資源(PHAsset)
        PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:fetchOptions];
    else {
        NSAssert(NO, @"Fetch collection not PHCollection: %@", collection);
    }
}

// 獲取所有資源的集合,並按資源的創建時間排序
PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];
// 這時 assetsFetchResults 中包含的,應該就是各個資源(PHAsset)
for (NSInteger i = 0; i < fetchResult.count; i++) {
    // 獲取一個資源(PHAsset)
    PHAsset *asset = fetchResult[i];
}
獲取圖像的方式與坑點
經過了上面的步驟,已經可以了解到如何在 PhotoKit 中獲取到代表資源的 PHAsset 了,但與 ALAssetLibrary 中從 ALAsset 中直接獲取圖像的方式不同,PhotoKit 無法直接從 PHAsset 的實例中獲取圖像,而是引入了一個管理器 PHImageManager 獲取圖像。PHImageManager 是通過請求的方式拉取圖像,並可以控制請求得到的圖像的尺寸、剪裁方式、質量,緩存以及請求本身的管理(發出請求、取消請求)等。而請求圖像的方法是 PHImageManager 的一個實例方法:
- (PHImageRequestID)requestImageForAsset:(PHAsset *)asset 
                          targetSize:(CGSize)targetSize 
                         contentMode:(PHImageContentMode)contentMode 
                             options:(nullable PHImageRequestOptions *)options 
                       resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler;
這個方法中的參數坑點不少,下面逐個參數列舉一下其作用及坑點:
asset,圖像對應的 PHAsset。
targetSize,需要獲取的圖像的尺寸,如果輸入的尺寸大於資源原圖的尺寸,則只返回原圖。需要注意在 PHImageManager 中,所有的尺寸都是用 Pixel 作為單位(Note that all sizes are in pixels),因此這裡想要獲得正確大小的圖像,需要把輸入的尺寸轉換為 Pixel如果需要返回原圖尺寸,可以傳入 PhotoKit 中預先定義好的常量 PHImageManagerMaximumSize,表示返回可選范圍內的最大的尺寸,即原圖尺寸。
contentMode,圖像的剪裁方式,與 UIView 的 contentMode 參數相似,控制照片應該以按比例縮放還是按比例填充的方式放到最終展示的容器內。注意如果 targetSize 傳入 PHImageManagerMaximumSize,則 contentMode 無論傳入什麼值都會被視為 PHImageContentModeDefault
options,一個 PHImageRequestOptions 的實例,可以控制的內容相當豐富,包括圖像的質量、版本,也會有參數控制圖像的剪裁,下面再展開說明。
resultHandler,請求結束後被調用的 block,返回一個包含資源對於圖像的 UIImage 和包含圖像信息的一個 NSDictionary,在整個請求的周期中,這個 block 可能會被多次調用,關於這點連同options 參數在下面展開說明。

(1)PHImageRequestOptions 與 iCloud 照片庫
PHImageRequestOptions 中包含了一系列控制請求圖像的屬性。
resizeMode 屬性控制圖像的剪裁,不知道為什麼 PhotoKit 會在請求圖像方法(requestImageForAsset)中已經有控制圖像剪裁的參數後(contentMode),還在 options 中加入控制剪裁的屬性,但如果兩個地方所控制的剪裁結果有所沖突,PhotoKit 會以 resizeMode 的結果為准。另外,resizeMode 也有控制圖像質量的作用。如 resizeMode 設置為 PHImageRequestOptionsResizeModeExact 則返回圖像必須和目標大小相匹配,並且圖像質量也為高質量圖像,而設置為 PHImageRequestOptionsResizeModeFast 則請求的效率更高,但返回的圖像可能和目標大小不一樣並且質量較低。
在 PhotoKit 中,對 iCloud 照片庫有很好的支持,如果用戶開啟了 iCloud 照片庫,並且選擇了“優化 iPhone/iPad 儲存空間”,或者選擇了“下載並保留原件”但原件還沒有加載好的時候,PhotoKit 也會預先拿到這些非本地圖像的 PHAsset,但是由於本地並沒有原圖,所以如果產生了請求高清圖的請求,PHotoKit 會嘗試從 iCloud 下載圖片,而這個行為最終的表現,會被 PHImageRequestOptions 中的值所影響。PHImageRequestOptions 中常常會用的幾個屬性如下:
networkAccessAllowed 參數控制是否允許網絡請求,默認為 NO,如果不允許網絡請求,那麼就沒有然後了,當然也拉取不到 iCloud 的圖像原件。deliveryMode 則用於控制請求的圖片質量。synchronous 控制是否為同步請求,默認為 NO,如果 synchronous 為 YES,即同步請求時,deliveryMode 會被視為 PHImageRequestOptionsDeliveryModeHighQualityFormat,即自動返回高質量的圖片,因此不建議使用同步請求,否則如果界面需要等待返回的圖像才能進一步作出反應,則反應時長會很長。
還有一個與 iCloud 密切相關的屬性 progressHandler,當圖像需要從 iCloud 下載時,這個 block 會被自動調用,block 中會返回圖像下載的進度,圖像的信息,出錯信息。開發者可以利用這些信息反饋給用戶當前圖像的下載進度以及狀況,但需要注意 progressHandler 不在主線程上執行,因此在其中需要操作 UI,則需要手工放到主線程執行。
上面有提到,requestImageForAsset 中的參數 resultHandler 可能會被多次調用,這種情況就是圖像需要從 iCloud 中下載的情況。在 requestImageForAsset 返回的內容中,一開始的那一次請求中會返回一個小尺寸的圖像版本,當高清圖像還在下載時,開發者可以首先給用戶展示這個低清的圖像版本,然後 block 在多次調用後,最終會返回高清的原圖。至於當前返回的圖像是哪個版本的圖像,可以通過 block 返回的 NSDictionary info 中獲知,PHImageResultIsDegradedKey 表示當前返回的 UIImage 是低清圖。如果需要判斷是否已經獲得高清圖,可以這樣判斷:

// 排除取消,錯誤,低清圖三種情況,即已經獲取到了高清圖
BOOL downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];

另外,當我們使用 requestImageForAsset 發出對圖像的請求時,如果在同一個 PHImageManager 中同時對同一個資源發出圖像請求,請求的進度是可以共享的,因此我們可以利用這個特性,把 PHImageManager 以單例的形式使用,這樣在切換界面時也不用擔心無法傳遞圖像的下載進度。例如,在圖像的列表頁面觸發了下載圖像,當我們離開列表頁面進入預覽大圖界面時,並不用擔心會重新圖像會重新下載,只要沒有手工取消圖像下載,進入預覽大圖界面下載圖像會自動繼續從上次的進度下載圖像。
如果希望取消下載圖像,則可以使用 PHImageManager 的 cancelImageRequest 方法,它傳入的是請求圖像的請求 ID,這個 ID 可以從 requestImageForAsset 的返回值中獲得,也可以從前面提到的包含圖像信息的 NSDictionary info 中獲得,當然前提是這個這個接收取消請求的 PHImageManager 與剛剛發出請求的 PHImageManager 是同一個實例,如上面所述使用單例是最為簡單有效的方式。
最後,還要介紹一個 PHImageRequestOptions 的屬性 versions,這個屬性是指獲取的圖像是否需要包含系統相冊“編輯”功能處理過的信息(如濾鏡,旋轉等),這一點比 ALAssetLibrary 要靈活很多,ALAssetLibrary 中並不能靈活地控制獲取的圖像是否帶有“編輯”處理過的效果,例如在 ALAsset 中獲取原圖的接口 fullResolutionImage 獲取到的是不帶“編輯”效果的圖像,要想獲取帶有“編輯”效果的圖像,只能自行處理獲取這些濾鏡效果,並手工疊加上去。在我們的 UI 框架 QMUI 中就有對獲取原圖作出這樣的封裝,整個過程也較為繁瑣,而框架中處理 PhotoKit 的部分則靈活很多,這也體現了 PhotoKit 相比 ALAssetLibrary 的最主要特點——復雜但靈活。文章的第三部分也會詳細列出如何處理這個問題。
(2)獲取圖像的優化
PHImageManager 提供了一個子類 PHImageCachingManager 用於處理圖像的緩存,但是這個子類並不只是圖像本身的緩存,而是更加實用——處理圖像的整個加載過程的緩存。例如要在一個 collectionView 上展示圖像列表這類大量的資源圖像的縮略圖時,可以利用 PHImageCachingManager 預先將一些圖像加載到內存中,這對優化 collectionView 滾動時的表現很有幫助。然而,這只是官方說法,實際上由於加載圖像的過程並不確定,每個業務加載圖像的實際需求都可能不一樣,因此 PHImageCachingManager 也采用比較松散的方法去控制這些緩存,其中的關鍵方法:

- (void)startCachingImagesForAssets:(NSArray *)assets targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options;

需要傳入一組 PHAsset,以及 targetSize,contentMode,以及一個 PHImageRequestOptions,如上面所述,這些參數之間的有著互相影響的作用,因此實際上不同的場景對於每個參數要求都不一樣,而這些參數的最佳取值也只能通過實際在場景中測試所得。因此,比起使用 PHImageCachingManager,我總結了一些更為簡易可行的緩存方法:
獲取圖片時盡量獲取預覽圖,不要直接顯示原件,建議獲取與設備屏幕同樣大小的圖像即可,實際上系統相冊預覽大圖時使用的也是預覽圖,這也是系統相冊加載速度快的原因。
獲取圖片使用異步請求,如上面所述,當請求為異步時返回圖像的 block 會被多次調用,先返回低清圖,再返回高清圖,這樣一來可以大大減少 UI 的等待時間。
獲取到高清圖後可以緩存下來,簡單地使用變量緩存即可,盡量在獲取到高清圖後避免再次發起請求獲取圖像。因為即使圖像原件已經下載下來,重新請求高清圖時因為圖片的尺寸比較大,因此系統生成圖像和剪裁圖像也會花費一些時間。
預先加載圖像,如像預覽大圖這類情景中,用戶同時只會看到一張大圖,因此在觀看某一張圖片時,預先請求其鄰近兩張圖片,對於加快 UI 的響應很有幫助。

經過實際測試,如果請求的是縮略圖(即尺寸小的圖像),那麼即使請求的圖像很多,仍不會產生任何不流暢的表現,但如果請求的是高清大圖,那麼即使只是同時請求幾張圖都會產生不流暢的狀況。如上面提到過的那樣,這些的狀況的出現很可能是請求大圖時由圖片元數據產生圖像,以及剪裁圖像的過程耗時較多。所以按實際表現來看,即使 PhotoKit 有自己的緩存策略,仍然很難避免這部分耗時。因此上面幾點優化獲取圖像的策略重點也是放在減少圖像大小,異步請求以及做緩存幾個方面。

 

 

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved