記一次gif模塊重構(gòu)嘗試

背景

之前公司APP中g(shù)if下載的主要代碼就一句話NSData *gifData = [NSData dataWithContentsOfURL:[NSURL URLWithString:newUrlString]];所有的下載都交給dataWithContentsOfURL:處理。這種做法太過簡單,隨著業(yè)務(wù)的壯大,很多需求滿足不了,并且這種方式不支持https,并且該方法阻塞線程,很多gif圖片較大會有明顯卡頓,因此急需自己創(chuàng)建一套gif下載器,用來展示gif圖片。

思路

gif圖片加載

gif圖片加載和普通圖片加載顯示過程基本一致,無非就是先從緩存中獲取,如果沒命中就從沙盒緩存中獲取,如果還沒命中就從遠(yuǎn)程下載并緩存。唯一有區(qū)別的是,gif在列表中只需要顯示一張靜態(tài)的圖片,在查看詳情的時(shí)候需要加載動圖,所以一個(gè)gif下載完成需要緩存2種圖片。

gif圖片緩存

緩存分兩種,內(nèi)存緩存和本地緩存。由于我們項(xiàng)目中使用SDWebImage,并且設(shè)置圖片最大開銷為100M,為了統(tǒng)一方便內(nèi)存管理,我們直接使用SDImageCache進(jìn)行讀取和存儲緩存。由于一張gif對應(yīng)一個(gè)靜態(tài)圖片和一個(gè)動態(tài)圖片,所以需要用兩個(gè)key來區(qū)別是動態(tài)圖還是靜態(tài)圖,但是一般一次下載圖片只有一個(gè)key值(默認(rèn)URL地址),所以在緩存圖片時(shí),在原來key值上加上前綴static_dynamic_來區(qū)別。

獲取key.png

沙盒緩存就比較簡單了,我們是創(chuàng)建了2個(gè)目錄,一個(gè)存放動態(tài)圖,一個(gè)存放靜態(tài)圖。由于iOS本地沙盒文件名稱不能太長(之前被坑過),所以我們采用md5(key+圖片唯一標(biāo)志)來保證名稱唯一性和長度。

gif圖片下載

下載的方式有很多種,這里我們打算采用AFNetworking中封裝好的AFURLSessionManagerdownloadTaskWithRequest:progress:destination:completionHandler:方法進(jìn)行下載,因?yàn)槠浜芎玫姆庋b了下載進(jìn)度和指定存儲地址,這正是我們業(yè)務(wù)上需要用到的。這里需要注意,AFURLSessionManager最好寫成單例,否則有內(nèi)存泄漏。

sessionManager.png

靜態(tài)gif生成

老版本中SDWebImage顯示gif圖片都是靜態(tài)的,不知道什么版本起就變成動態(tài)的了,這里用原始gif生成靜態(tài)圖片的代碼就是參考老版本SDWebImage庫中的代碼。


靜態(tài)gif生成.png

圖片的展示形式

由于我們項(xiàng)目中是采用SDWebImage進(jìn)行圖片展示,因此為了代碼上的統(tǒng)一,gif也采用UIImageView類別的形式進(jìn)行加載圖片。


圖片加載.png

可以看到,加載方式比較簡單明了,兩個(gè)set方法和一個(gè)cancel方法就搞定動態(tài)和靜態(tài)gif,還帶有下載進(jìn)度。

cell復(fù)用等問題

為了解決cell復(fù)用問題,實(shí)際上就是再每次賦值時(shí)取消UIImageView上次任務(wù)的block回調(diào),用新的回調(diào)替換它。我們可以創(chuàng)建一個(gè)隊(duì)列,可以采用NSOperationQueue和信號量結(jié)合,實(shí)現(xiàn)控制每次最多只有3個(gè)線程下載圖片,每次下載任務(wù)都是以NSBlockOperation形式加入隊(duì)列,每次下載任務(wù)前檢查下任務(wù)是否被取消,如果取消了就跳過該任務(wù)。這樣就可以簡單得實(shí)現(xiàn)cell復(fù)用問題的規(guī)避。這個(gè)原理和SDWebImage有點(diǎn)類似,具體可以參考其內(nèi)部實(shí)現(xiàn)。

代碼實(shí)現(xiàn)后遇到的問題

SDImageCache計(jì)算內(nèi)存cost不準(zhǔn)確

之前為了方便內(nèi)存管理和圖方便,采用SDImageCache進(jìn)行緩存gif靜態(tài)圖和原圖。但是實(shí)際測試后發(fā)現(xiàn),明明限制最大內(nèi)存消耗為100M,但是實(shí)際上會遠(yuǎn)遠(yuǎn)超過。排查后發(fā)現(xiàn)問題出現(xiàn)在這:


內(nèi)存1.png

memCache計(jì)算cost主要是靠SDCacheCostForImage()方法,而SD計(jì)算Image在內(nèi)存中的大小居然只是簡單的根據(jù)公式:圖片大小=圖片長度x圖片高度x圖片比例x圖片比例


內(nèi)存2.png

一個(gè)gif原圖里面有多張圖片組成,如果按這個(gè)計(jì)算方法,只能算計(jì)出一張圖片的大小,其誤為gif有多少張就差多少倍(還不算其他時(shí)間等信息)。

再后來進(jìn)過嘗試,如果把Gif原圖存內(nèi)存中,隨便一張網(wǎng)上下載的gif圖片就占用了200M內(nèi)存,遠(yuǎn)超限制100M,所以將gif原圖存儲到內(nèi)存中不是一個(gè)明智的選擇,所以我們對gif只采用沙盒緩存,不采用內(nèi)存緩存,實(shí)際測試效果不會差太多。

Cell復(fù)用,劃出屏幕的gif動圖未釋放

上面所述,gif顯示會占用大量內(nèi)存,一般都放在cell中的UIImageView中。這里叫考慮cell的復(fù)用了,由于cell復(fù)用機(jī)制,cell的imageView自然也不會釋放,那么image也不會釋放,存在內(nèi)存中,所以如果是動態(tài)gif圖片顯示,還需要主要當(dāng)cell劃出顯示區(qū)域后手動將UIImageView.image置為nil,減少內(nèi)存占用。

總結(jié)

總體下載,本次嘗試還是比較順利的,中間遇到一些問題,最后也能得到很好的解決。由于gif圖片較大,還測試出很多問題,比如長圖,超大圖片都有類似的內(nèi)存問題。所以之后在內(nèi)存管理方面還是要多留一個(gè)心眼,多測試極端情況。

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

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

  • 夜鶯2517閱讀 128,214評論 1 9
  • 版本:ios 1.2.1 亮點(diǎn): 1.app角標(biāo)可以實(shí)時(shí)更新天氣溫度或選擇空氣質(zhì)量,建議處女座就不要選了,不然老想...
    我就是沉沉閱讀 7,510評論 1 6
  • 我是一名過去式的高三狗,很可悲,在這三年里我沒有戀愛,看著同齡的小伙伴們一對兒一對兒的,我的心不好受。怎么說呢,高...
    小娘紙閱讀 3,884評論 4 7
  • 這些日子就像是一天一天在倒計(jì)時(shí) 一想到他走了 心里就是說不出的滋味 從幾個(gè)月前認(rèn)識他開始 就意識到終究會發(fā)生的 只...
    栗子a閱讀 1,728評論 1 3

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