BBWebImage 設計思路
BBWebImage 是高性能 Swift 圖片組件,用於圖片下載、緩存、編解碼、編輯與展示。
GitHub 地址: https://github.com/Silence-GitHub/BBWebImage
效果圖
下載、展示並緩存原圖
下載、漸進式解碼、編輯圖片,緩存編輯后的圖片至內存 (Memory)、緩存原圖數據至磁盤 (Disk)
- 添加濾鏡
- 繪制圓角、邊框
性能對比
測試的圖片組件有 BBWebImage (1.1.0)、SDWebImage (4.4.6 以及 FLAnimatedImage 1.0.12 用於測試 GIF)、YYWebImage (1.0.5) 和 Kingfisher (4.10.1)。測試設備是 iPhone 7,iOS 12.1。
- BBWebImage 的內存緩存與磁盤緩存速度很快,針對縮略圖的優勢明顯
- 加載展示 GIF,BBWebImage 占用很少的 CPU 和內存
為什么寫這個圖片組件
寫 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