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

一.PhotoKit 簡介

PhotoKit 是一套比 AssetsLibrary 更完整也更高效的庫,對資源的處理跟 AssetsLibrary 也有很大的不同。

首先簡單介紹幾個概念:
PHAsset: 代表照片庫中的一個資源,跟 ALAsset 類似,通過 PHAsset 可以獲取和保存資源
PHFetchOptions: 獲取資源時的參數(shù),可以傳 nil,即使用系統(tǒng)默認(rèn)值
PHFetchResult: 表示一系列的資源集合,也可以是相冊的集合
PHAssetCollection: 表示一個相冊或者一個時刻,或者是一個「智能相冊(系統(tǒng)提供的特定的一系列相冊,例如:最近刪除,視頻列表,收藏等等,如下圖所示)
PHImageManager: 用于處理資源的加載,加載圖片的過程帶有緩存處理,可以通過傳入一個 PHImageRequestOptions 控制資源的輸出尺寸等規(guī)格
PHImageRequestOptions: 如上面所說,控制加載圖片時的一系列參數(shù)
這里還有一個額外的概念PHCollectionList,表示一組?PHCollection,它本身也是一個?PHCollection,因此?PHCollection 作為一個集合,可以包含其他集合,這使到 PhotoKit 的組成比 ALAssetLibrary 要復(fù)雜一些。另外與 ALAssetLibrary 相似,一個 PHAsset 可以同時屬于多個不同的 PHAssetCollection,最常見的例子就是剛剛拍攝的照片,至少同時屬于“最近添加”、“相機(jī)膠卷”以及“照片 - 精選”這三個 PHAssetCollection。關(guān)于這幾個概念的關(guān)系如下圖:

二. PhotoKit 的機(jī)制

1. 獲取資源

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

// 列出所有相冊智能相冊
let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum,    subtype: .albumRegular, options: nil)
// 列出所有用戶創(chuàng)建的相冊
let topLevelUserCollections = PHCollectionList.fetchTopLevelUserCollections(with: nil)
// 獲取所有資源的集合,并按資源的創(chuàng)建時間排序
let options = PHFetchOptions()
options.sortDescriptors = [NSSortDescriptor.init(key: "creationDate", ascending: true)]
let assetsFetchResults = PHAsset.fetchAssets(with: options)

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

// 列出所有相冊智能相冊
    let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options: nil)
    for i in 0..<smartAlbums.count {
        let collection: PHAssetCollection = smartAlbums[i]
        // 從每一個智能相冊中獲取到的 PHFetchResult 中包含的才是真正的資源(PHAsset)
        let fetchResult = PHAsset.fetchAssets(in: collection, options: nil)
    }
    
    // 列出所有用戶創(chuàng)建的相冊
    let topLevelUserCollections = PHCollectionList.fetchTopLevelUserCollections(with: nil)
    
    // 獲取所有資源的集合,并按資源的創(chuàng)建時間排序
    let options = PHFetchOptions()
    options.sortDescriptors = [NSSortDescriptor.init(key: "creationDate", ascending: true)]
    let assetsFetchResults = PHAsset.fetchAssets(with: options)
    for i in 0..<assetsFetchResults.count {
        // 獲取一個資源(PHAsset)
        let asset: PHAsset = assetsFetchResults[i]
    }

2. 獲取圖像的方式與坑點(diǎn)

 經(jīng)過了上面的步驟,已經(jīng)可以了解到如何在 PhotoKit 中獲取到代表資源的 PHAsset 了,但與 ALAssetLibrary 中從 ALAsset 中直接獲取圖像的方式不同,PhotoKit 無法直接從 PHAsset 的實(shí)例中獲取圖像,而是引入了一個管理器?PHImageManager 獲取圖像。PHImageManager 是通過請求的方式拉取圖像,并可以控制請求得到的圖像的尺寸、剪裁方式、質(zhì)量,緩存以及請求本身的管理(發(fā)出請求、取消請求)等。而請求圖像的方法是 ?PHImageManager 的一個實(shí)例方法:


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

這個方法中的參數(shù)坑點(diǎn)不少,下面逐個參數(shù)列舉一下其作用及坑點(diǎn):

1.asset,圖像對應(yīng)的 PHAsset。
2.targetSize,需要獲取的圖像的尺寸,如果輸入的尺寸大于資源原圖的尺寸,則只返回原圖。需要注意在 PHImageManager 中,所有的尺寸都是用 Pixel 作為單位(Note that all sizes are in pixels),因此這里想要獲得正確大小的圖像,需要把輸入的尺寸轉(zhuǎn)換為 Pixel。如果需要返回原圖尺寸,可以傳入 PhotoKit 中預(yù)先定義好的常量?PHImageManagerMaximumSize,表示返回可選范圍內(nèi)的最大的尺寸,即原圖尺寸。
3.contentMode,圖像的剪裁方式,與?UIView 的 contentMode 參數(shù)相似,控制照片應(yīng)該以按比例縮放還是按比例填充的方式放到最終展示的容器內(nèi)。注意如果 targetSize 傳入PHImageManagerMaximumSize,則 contentMode 無論傳入什么值都會被視為PHImageContentModeDefault。
4.options,一個?PHImageRequestOptions 的實(shí)例,可以控制的內(nèi)容相當(dāng)豐富,包括圖像的質(zhì)量、版本,也會有參數(shù)控制圖像的剪裁,下面再展開說明。
5.resultHandler,請求結(jié)束后被調(diào)用的 block,返回一個包含資源對于圖像的 UIImage 和包含圖像信息的一個 NSDictionary,在整個請求的周期中,這個 block 可能會被多次調(diào)用,關(guān)于這點(diǎn)連同 options 參數(shù)在下面展開說明。

3.獲取圖像的優(yōu)化

PHImageManager 提供了一個子類?PHImageCachingManager 用于處理圖像的緩存,但是這個子類并不只是圖像本身的緩存,而是更加實(shí)用——處理圖像的整個加載過程的緩存。例如要在一個?collectionView 上展示圖像列表這類大量的資源圖像的縮略圖時,可以利用 PHImageCachingManager?預(yù)先將一些圖像加載到內(nèi)存中,這對優(yōu)化 collectionView 滾動時的表現(xiàn)很有幫助。然而,這只是官方說法,實(shí)際上由于加載圖像的過程并不確定,每個業(yè)務(wù)加載圖像的實(shí)際需求都可能不一樣,因此PHCachingImageManager 也采用比較松散的方法去控制這些緩存,其中的關(guān)鍵方法:

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

需要傳入一組 PHAsset,以及 targetSize,contentMode,以及一個?PHImageRequestOptions,如上面所述,這些參數(shù)之間的有著互相影響的作用,因此實(shí)際上不同的場景對于每個參數(shù)要求都不一樣,而這些參數(shù)的最佳取值也只能通過實(shí)際在場景中測試所得。因此,比起使用?PHImageCachingManager,我總結(jié)了一些更為簡易可行的緩存方法:

  1. 獲取圖片時盡量獲取預(yù)覽圖,不要直接顯示原件,建議獲取與設(shè)備屏幕同樣大小的圖像即可,實(shí)際上系統(tǒng)相冊預(yù)覽大圖時使用的也是預(yù)覽圖,這也是系統(tǒng)相冊加載速度快的原因。
    2.獲取圖片使用異步請求,如上面所述,當(dāng)請求為異步時返回圖像的 block 會被多次調(diào)用,先返回低清圖,再返回高清圖,這樣一來可以大大減少 UI 的等待時間。
    3.獲取到高清圖后可以緩存下來,簡單地使用變量緩存即可,盡量在獲取到高清圖后避免再次發(fā)起請求獲取圖像。因為即使圖像原件已經(jīng)下載下來,重新請求高清圖時因為圖片的尺寸比較大,因此系統(tǒng)生成圖像和剪裁圖像也會花費(fèi)一些時間。
    4.預(yù)先加載圖像,如像預(yù)覽大圖這類情景中,用戶同時只會看到一張大圖,因此在觀看某一張圖片時,預(yù)先請求其鄰近兩張圖片,對于加快 UI 的響應(yīng)很有幫助。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容