BBWebImage 設計思路 BBWebImage 是 Swift 圖片組件,用於圖片下載、緩存、編解碼、編輯與展示。 GitHub 地址: "https://github.com/Silence GitHub/BBWebImage" 效果圖 下載、展示並緩存原圖 下載、漸進式解碼、編輯圖片,緩存 ...
BBWebImage 設計思路
BBWebImage 是 Swift 圖片組件,用於圖片下載、緩存、編解碼、編輯與展示。
GitHub 地址: https://github.com/Silence-GitHub/BBWebImage
效果圖
下載、展示並緩存原圖
下載、漸進式解碼、編輯圖片,緩存編輯後的圖片至記憶體 (Memory)、緩存原圖數據至磁碟 (Disk)
- 添加濾鏡
- 繪製圓角、邊框
為什麼寫這個圖片組件
寫 BBWebImage 最開始的目的是要解決現有圖片組件中圖片編輯與動圖的問題。
做過的項目中,圖片組件都主要用 SDWebImage,顯示 WebP、APNG 等格式動圖用 YYWebImage。這些圖片組件都非常優秀,能滿足大多數使用場景的需求。YYWebImage 支持的圖片格式很多,但是功能和可自定義程度不如 SDWebImage (例如自定義圖片解碼器)。當 BBWebImage 第一版 0.1.0 發佈時,SDWebImage 的最新正式版是 4.4.3,還沒有圖片編輯模塊。有些時候需要展示編輯後的圖片,例如添加濾鏡、繪製圓角和邊框 (防止 CALayer 設置圓角造成頓卡) 等,也需要緩存編輯後的圖片。如果用 SDWebImage 下載圖片並編輯,會有以下問題:
- 如果只緩存編輯後的圖片,則展示原圖需要再次下載。
- 假設原圖數據緩存至磁碟。如果不緩存編輯後的圖片,需要在每次展示前重覆編輯原圖這個步驟。如果把編輯後的圖片緩存至記憶體和磁碟,為了與原圖區分,需要維護 cache key (在緩存中一個 key 對應原圖,另一個 key 對應編輯後的圖片)。如果把編輯後的圖片只緩存至記憶體,則為了區分從緩存中取出的圖片是否經過編輯,需要判斷是從記憶體還是磁碟取到的圖片。
- 如果用 Core Graphics 框架編輯圖片,SDWebImage 的圖片解壓縮 (Decompress) 是不必要的。編輯和解壓縮步驟類似:創建 CGContext、繪製圖片、創建新圖片。需要在編輯前禁用圖片解壓縮,完成後再啟用。
另外,SDWebImage 的圖片降採樣 (Downsample) 用了統一處理的方式,圖片解析度大於固定閾值是降採樣的必要條件。問題就在於閾值是固定的,遇到多張大圖的情況,這個閾值還是太大,導致記憶體占用過多而崩潰。如果圖片組件中有圖片編輯模塊,可以把圖片降採樣放入編輯模塊,就可以自定義降採樣參數,從而解決記憶體占用過多問題。
關於動圖,SDWebImage 用 FLAnimatedImage 來展示 GIF,但是有性能問題。原因是 FLAnimatedImage 沒有繼承 UIImage,SDWebImage 的解碼器無法直接返回 FLAnimatedImage,只好在主線程中用圖片數據創建 FLAnimatedImage,這一步阻塞主線程導致頓卡。具體代碼分析和解決方案參見 SDWebImage 載入顯示 GIF 與性能問題。解決方案能用,但是從設計的角度看,SDWebImage 使用 FLAnimatedImage 並不合適。FLAnimatedImage 只適用於 GIF,無法通過自定義解碼器來支持其他格式的動圖。理想的情況是,圖片組件搭建好展示動圖的框架,有常用動圖的解碼,可以自定義解碼器來支持其他格式的動圖。
架構設計
主要結構
BBWebImage 的主要結構可以看下麵這幅圖。BBImageCache 管理圖片緩存,BBImageDownloader 管理圖片下載,BBImageCoderManager 管理圖片編解碼,BBWebImageEditor 提供圖片編輯方法。BBWebImageManager 調用前四者的方法實現相應功能,對外提供一個方法實現圖片載入 (緩存讀取與下載)、解碼、編輯和緩存。UIImageView 的擴展方法調用 BBWebImageManager 的方法獲取圖片用於展示。動圖封裝成 BBAnimatedImage,用 BBAnimatedImageView 展示。
BBImageCache
BBImageCache 是圖片緩存協議,定義向緩存存取圖片的行為。BBLRUImageCache 是預設使用的緩存,遵循 BBImageCache 協議。BBLRUImageCache 裡面有記憶體緩存與磁碟緩存,都採用 LRU 演算法 (Least recently used)。這部分設計基本參照 YYCache。記憶體緩存用字典和雙向鏈表實現 LRU 演算法。磁碟緩存用 SQLite 資料庫存儲數據相關信息 (key、大小、更新時間等),二進位數據本身根據文件大小來決定存儲至 SQLite 資料庫或者直接寫入沙盒目錄。
往記憶體緩存中保存的是 UIImage,取出的也是 UIImage。往磁碟緩存中存儲的是 Data 或者是 UIImage,後者會被編碼成 Data;取出的只是 Data,這裡不會進行解碼 (BBWebImageManager 拿到數據,才會用 BBImageCoderManager 進行解碼)。
如果預設緩存無法滿足需求,可以自定義緩存,遵循 BBImageCache 協議,替換預設緩存。
BBImageDownloader
BBImageDownloader 是圖片下載協議,定義圖片下載行為。BBMergeRequestImageDownloader 是預設使用的下載器,遵循 BBImageDownloader 協議。BBMergeRequestImageDownloader 會合併對同一 URL 的網路請求,防止對同一 URL 發出重覆請求。每一個下載任務封裝成 BBImageDownloadTask (是個協議,預設實現是 BBImageDefaultDownloadTask,可自定義實現) ,包含這次下載任務的完成回調等信息。每一個 URL 網路請求 (以下稱為 "下載操作") 封裝成 BBImageDownloadOperation (也是協議,預設實現是 BBMergeRequestImageDownloadOperation,可自定義實現),包含至少一個下載任務。
下載操作的執行順序是,一般操作 (下載圖片後要立即使用) 優先於預載入操作 (圖片不需要在下載後立即使用,只是下載存入緩存),同時先進先出,也就是老的操作優先執行。雖然 SDWebImage 提供了後進先出和設置優先順序的功能,但在做過的項目中並沒有用到。因此這裡沒有設計這些功能,以後需要的話可以加上。實現方法原來是用自帶的 Operation 和 OperationQueue 實現,但後來想把這一部分也自定義,於是用字典和雙向鏈表實現。一共有兩組字典和雙向鏈表的組合,一組代表一般操作隊列,另一組代表預載入操作隊列。最多同時執行操作數為 6 個。操作數少於 6,有新操作進來就執行;大於等於 6,把新操作插入相應隊列尾部。一個操作結束後,先從一般操作隊列頭部取一般操作來執行,沒有的話再從預載入操作隊列頭部取預載入操作來執行。預載入操作還可以升級為一般操作。如果前面有預載入任務,並且相應的預載入操作進入預載入操作隊列等待,後來有一般下載任務是相同的 URL,則之前的預載入操作會被移出預載入操作隊列,進入一般操作操作隊列而升級為一般操作,把後來的一般下載任務合併進來。
如果需要自定義圖片下載行為,例如 MD5 校驗等,可以考慮自定義下載任務 (遵循 BBImageDownlaodTask 協議) 或下載操作 (遵循 BBImageDownloadOperation 協議),甚至自定義整個下載器 (遵循 BBImageDownloader 協議)。
BBImageCoderManager
BBImageCoder 是圖片編解碼協議,定義圖片編解碼行為。BBImageCoderManager 遵循 BBImageCoder 協議,包含至少一個編解碼器 (也遵循 BBImageCoder 協議)。用 BBImageCoderManager 來編解碼時,BBImageCoderManager 會遍歷其中的編解碼器,嘗試找到一個能完成操作的編解碼器。這個圖片組件中的所有圖片編解碼操作 (包括靜圖、漸進式解碼、動圖) 都由遵循 BBImageCoder 協議的編解碼器完成,可以通過自定義編解碼器來自定義編解碼行為,支持不同格式的圖片。
BBWebImageEditor
BBWebImageEditor 是個結構體,包含一個字元串 key 和一個閉包 edit。閉包 edit 輸入一個 UIImage, 輸出一個 UIImage,用於編輯圖片。字元串 key 作為圖片編輯方法的唯一標識符,將與 edit 輸出的 UIImage 動態關聯 (通過擴展屬性 bb_imageEditKey 來訪問,以下稱為 “edit key”)。例如,定義一個添加濾鏡的圖片編輯器,edit 是添加濾鏡閉包,key 是 "filter",編輯後的圖片的 edit key 是 "filter";定義一個繪製圓角的圖片編輯器,edit 是繪製圓角閉包,key 是 "roundedCorner",編輯後的圖片的 edit key 是 "roundedCorner"。原圖的 edit key 為 nil。通過圖片的 edit key 就可以知道圖片是原圖還是某個編輯器編輯後的圖片。
BBWebImageManager
BBWebImageManager 對外提供載入圖片的方法 loadImage(with:)
。與 SDWebImage 類似,先在記憶體緩存中找圖片,沒有的話找磁碟緩存,如果沒有就下載並緩存圖片。不同的是,圖片解壓縮在這一層才執行 (SDWebImage 在 cache 和 download operation 中都有執行),而且這裡還有圖片編輯步驟。loadImage(with:)
方法的 editor 參數是 BBwebImageEditor? 類型,傳 nil 表示需要原圖,傳某個編輯器表示需要用原圖進行編輯。如果從記憶體緩存中取到圖片,需要通過 edit key 判斷圖片的編輯狀態 (原圖、或者被編輯),決定後續步驟 (直接使用圖片,直接編輯圖片,需要從磁碟緩存或網路獲取圖片)。如果傳入了編輯器作為方法參數,則不進行圖片解壓縮,解壓縮由編輯器負責。原圖數據保存至磁碟緩存,原圖或編輯後的圖片保存至記憶體緩存,通過圖片的 edit key 來區分編輯狀態。
這個圖片組件內置的圖片編輯器中有一個比較常用,通過 bb_imageEditorCommon(with:)
方法創建,傳入的參數有 imageView 的大小和 contentMode、期望最大解析度、圓角位置和圓角半徑、邊框寬度和顏色、背景色。編輯器裁剪圖片,只保留 imageView 顯示的部分;根據 imageView 的大小與期望最大解析度計算降採樣解析度閾值,如果原圖解析度大於閾值就會進行降採樣;繪製圓角、邊框、背景色。可以用這個圖片編輯器自定義降採樣解析度閾值,防止記憶體占用過多;繪製好圓角和邊框,防止 CALayer 設置圓角和邊框造成頓卡。
UIImageView 擴展
BBWebCache 是圖片載入協議,定義圖片載入行為。預設實現了圖片載入方法 bb_setImage(with:)
(以下稱為 "協議載入方法"),用 BBWebImageManager 的單例載入圖片,動態關聯 BBWebCacheOperation 對象用於訪問圖片載入任務 (方便以後取消任務)。UIImageView 遵循 BBWebCache 協議,載入 image 和 highlightedImage 的擴展方法,都直接調用協議載入方法,只是傳入的參數有所不同。與此類似,UIButton、CALayer、MKAnnotationView 都有相應的擴展方法用於載入圖片,也是直接調用協議載入方法。如果有自定義的 view 甚至 object 需要載入圖片,也可以遵循 BBWebCache 協議,調用協議載入方法來實現載入圖片的擴展方法。
動圖
動圖封裝成 BBAnimatedImage,繼承 UIImage。初始化方法除了圖片數據還有動圖解碼器,如果沒有指定解碼器,則從 BBWebImageManager 單例的 BBImageCoderManager 的解碼器中尋找,有合適的解碼器才能初始化動圖。動圖向解碼器獲取每一幀圖片以及動畫時間等信息,並管理圖片幀的緩存。根據總記憶體容量、可用記憶體容量來動態計算最大緩存容量,以此來清除暫時不用的圖片幀同時保存將要展示的圖片幀。也支持自定義最大緩存容量。動圖有 bb_editor 屬性,是 BBWebImageEditor? 類型。用某個圖片編輯器給這個屬性賦值,則會對圖片幀進行編輯。這個屬性預設為空,表示使用原始圖片幀。
BBAnimatedImageView 繼承 UIImageView,用來展示動圖。用 CADisplayLink 播放動畫。屏幕刷新時,向動圖獲取當前要展示的圖片幀。這裡只從緩存的圖片幀中獲取,避免解碼阻塞主線程。同時,告訴動圖下一幀要展示的圖片是第幾幀,由動圖進行後臺解碼。動圖會在 App 進入後臺時、從 imageView 上移除時、以及收到記憶體警告時,清除緩存的圖片幀。
BBAnimatedImageView 除了展示動圖,也可以展示靜圖。它本身就繼承 UIImageView,可以當作普通的 UIImageView 來用。BBAnimatedImage 本身繼承 UIImage,與編解碼協議 BBImageCoder 相符,可以在解碼器中解碼出來,這一點與普通的靜圖相同,不像 SDWebImage + FLAnimatedImage 那樣靜圖與 GIF 不相符 (導致要對 GIF 特殊處理)。在這個框架基礎上,通過自定義圖片編解碼器就可以支持其他格式的動圖 (當然也可以支持其他格式的靜圖,只是這部分在講動圖)。
總結
BBWebImage 的圖片緩存、下載、編解碼、編輯功能都可以自定義。把動圖封裝成繼承 UIImage 的類,用繼承 UIImageView 的類進行展示,支持編輯動圖的圖片幀。可以自定義編解碼器支持其他格式的圖片。現在 BBWebImage 搭建了框架,之後會逐步完善細節。如果有編輯靜圖或動圖的需求,或者其他相關需求,可以嘗試 BBWebImage。源碼及使用方法見 GitHub: https://github.com/Silence-GitHub/BBWebImage
轉載請註明出處:https://www.cnblogs.com/silence-cnblogs/p/10442984.html