深入分析iOS應(yīng)用中對于圖片緩存的管理和使用
我們的 iOS 應(yīng)用都包含了大量的圖像。創(chuàng)建富有吸引力的視圖,主要依賴于大量的裝飾圖片,所有這些首先必須從遠(yuǎn)程服務(wù)器獲取。如果每次打開應(yīng)用都要從服務(wù)器一次又一次的獲取每個(gè)圖像,那么用戶體驗(yàn)肯定達(dá)不到好的效果,所以本地緩存遠(yuǎn)程圖像是非常有必要的。
兩種方式加載本地圖片
1.通過imageNamed:方法加載圖片
用過這種方式加載圖片,一旦圖片加載到內(nèi)存中,那么就不會(huì)銷毀,一直到程序退出。(也就是說imageNamed:會(huì)有圖片緩存的功能,當(dāng)下次訪問圖片的時(shí)候速度會(huì)更快。)
用這種方式加載圖片,圖片的內(nèi)存管理并不受程序員控制。
UIImage *image = [UIImage imageNamed: @“image”]
的意思是創(chuàng)建一個(gè)UIImage對象,并不是說image這個(gè)本身就是一張圖片,而是image指向一張圖片。在創(chuàng)建這個(gè)對象的時(shí)候?qū)嶋H上并沒有把真正的圖片加載到內(nèi)存里,而是等到用到圖片的時(shí)候才會(huì)加載。
如上例,如果把image對象設(shè)置為nil,如果是其它對象,那么沒有強(qiáng)指針指向一個(gè)對象,這個(gè)對象就會(huì)銷毀;但是即使image = nil,它會(huì)指向的圖片資源也不會(huì)銷毀。
2.通過imageWithContentsOfFile:方式加載圖片
使用這個(gè)方法加載圖片,當(dāng)指向圖片對象的指針銷毀或指向其它對象,這個(gè)圖片對象沒有其它強(qiáng)指針指向,這個(gè)圖片對象會(huì)銷毀,不會(huì)一直在內(nèi)存中停留。
因?yàn)闆]有緩存,所以如果相同的圖片多次加載,那么也會(huì)有多個(gè)圖片對象來占用內(nèi)存,而不是用緩存的圖片。
使用這個(gè)方法,需要file的全路徑(之前用NSString, NSArray之類的加載文件也是一樣的,比如stringWithContentsOfFile:,看到file就知道是需要傳入全路徑。)
NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"png"];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
注意如果圖片在Images.xcassets中,是不能使用這個(gè)方法的。所以說想要自己進(jìn)行圖片的內(nèi)存管理(不希望有緩存圖片),那么要將圖片資源直接拖入工程,而不是放在Images.xcassets中。
快速隊(duì)列和慢速隊(duì)列
我們設(shè)置了兩個(gè)隊(duì)列,一個(gè)串行,一個(gè)并行。在屏幕上被迫切要求的圖片進(jìn)入并行隊(duì)列(fastQueue),可能晚點(diǎn)才需要的圖片進(jìn)入串行隊(duì)列(slowQueue)。
就UITableView的實(shí)現(xiàn)而言,這意味著在屏幕上的表格單元從fastQueue獲取圖片, 每個(gè)關(guān)閉的屏幕行的圖片從slowQueue預(yù)加載。
現(xiàn)在不需要處理圖片
假設(shè)我們要從服務(wù)器上請求包含30條事件的一頁資訊回來,一旦這些內(nèi)容請求回來時(shí)我們就可以排隊(duì)等待預(yù)取其中的每一張圖。
- (void)pageLoaded:(NSArray *)newEvents {
for (SGEvent *event in newEvents) {
[SGImageCache slowGetImageForURL:event.imageURL thenDo:nil];
}
}
slowGetImageForURL:這個(gè)方法將圖片添加到slowQueue這個(gè)隊(duì)列當(dāng)中,允許它們在不阻塞網(wǎng)絡(luò)通信的前提下被一張一張的取出來。
thenDo:這個(gè)代碼塊在這里是沒有被實(shí)現(xiàn),是因?yàn)槲覀兡壳斑€不需要對圖片做任何事情。所有我們需要做的就是確保它們在本地磁盤緩存當(dāng)中,并且隨時(shí)準(zhǔn)備在屏幕上滑動(dòng)表格時(shí)來使用。
現(xiàn)在就要處理圖片
顯示在屏幕上的表格希望立即顯示它們的圖片,所以在table cell子類當(dāng)中實(shí)現(xiàn):
- (void)setEvent:(SGEvent *)event {
__weak SGEventCell *me = self;
[SGImageCache getImageForURL:event.imageURL thenDo:^(UIImage *image) {
me.imageView.image = image; }
];
}
getImageForURL:這個(gè)方法將抓取圖片的過程添加到fastQueue這個(gè)隊(duì)列當(dāng)中,意味著只要iOS系統(tǒng)允許,它們會(huì)并行被地執(zhí)行。如果抓取圖片的過程已經(jīng)存在于slowQueue隊(duì)列當(dāng)中,它會(huì)被移動(dòng)到fastQueue隊(duì)列中,從而避免重復(fù)請求。
一直異步
等等,getImageForURL:不是一個(gè)異步方法嗎?如果你明知道圖片已經(jīng)在緩存中,但是卻不想在主線程上立即使用它嗎?直覺告訴你那是錯(cuò)誤的。
從磁盤上加載圖片太費(fèi)資源,同樣解壓圖片也會(huì)費(fèi)很多資源??梢栽诨瑒?dòng)的過程當(dāng)中進(jìn)行配置和添加表格,這最后一件你想在滑動(dòng)表格時(shí)做的事是很危險(xiǎn)地,因?yàn)樗鼤?huì)阻塞主線程,會(huì)有卡頓的現(xiàn)象出現(xiàn)。
使用getImageForURL:可以讓磁盤加載的動(dòng)作脫離主線程,于是當(dāng)thenDo:這個(gè)用于收尾工作的代碼塊執(zhí)行的時(shí)候它已經(jīng)有了一個(gè)UIImage實(shí)例,從而不會(huì)有滑動(dòng)卡頓的危險(xiǎn)。如果圖片已經(jīng)存在于本地緩存當(dāng)中,用于收尾工作的代碼塊會(huì)在下一次運(yùn)行周期執(zhí)行,并且用戶不會(huì)注意到兩者之間的差別。他們會(huì)注意到的是滑動(dòng)不會(huì)卡頓了。
現(xiàn)在,不需要你快速執(zhí)行
如果用戶很快的滑動(dòng)表格到底部,幾十或幾百個(gè)表格單元會(huì)出現(xiàn)在屏幕上,并向fastQueue請求圖片數(shù)據(jù),然后很快地從屏幕上消失。突然間這個(gè)并行地隊(duì)列會(huì)將大量實(shí)際上不再需要的圖片請求充斥進(jìn)網(wǎng)絡(luò)。當(dāng)用戶最終停止滑動(dòng)時(shí),那些當(dāng)前屏幕上相應(yīng)的表格單元視圖會(huì)將它們的圖片請求至于那些并不急需的請求后面,因此網(wǎng)絡(luò)阻塞了。
這就是 wheremoveTaskToSlowQueueForURL:這個(gè)方法的產(chǎn)生的原因.
// a table cell is going off screen-
(void)tableView:(UITableView *)table
didEndDisplayingCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath*)indexPath {
// we don't need it right now, so move it to the slow queue
[SGImageCache moveTaskToSlowQueueForURL:[[(id)cell event] imageURL]];
}
這確保在fastQueue中的只會(huì)有真正需要被快速執(zhí)行的任務(wù)。任何以前認(rèn)為需要快速執(zhí)行但現(xiàn)在不需要的任務(wù)會(huì)被移至slowQueue中。
重點(diǎn)和選擇
已經(jīng)有相當(dāng)多的iOS圖片緩存庫。它們中一些庫只針對某些應(yīng)用場景,一些庫提供了不同場景一定的可擴(kuò)展性。我們的庫即沒有專門針對某些應(yīng)用場景,也沒有太多大而全的特性。針對我們的用戶我們有三類基本的重點(diǎn):
重點(diǎn) 1: 最好的幀率
很多的庫都非常專注在這一點(diǎn)上,使用一些高度定制和復(fù)雜的方法,盡管基準(zhǔn)沒有決定性地顯示這樣有效。我們發(fā)現(xiàn)最好的幀率由這些決定:
將對磁盤的訪問(并且?guī)缀跗渌乃校┟撾x主線程。
使用UIImage的內(nèi)存緩存來避免不必要的磁盤訪問和圖片解壓。
重點(diǎn) 2: 讓最最重要的圖片優(yōu)先顯示
大多數(shù)的庫都考慮讓隊(duì)列管理成為別人關(guān)心的事。對于我們的應(yīng)用,這幾乎是最重要的點(diǎn)。
讓正確的圖片在正確的時(shí)間顯示在屏幕上可以歸結(jié)為一個(gè)簡單的問題:“我們現(xiàn)在就需要它顯示還是過一會(huì)兒?”。那些需要立即顯示的圖片是并行加載地,而其它所有東西都被添加到串行隊(duì)列中。所有之前急迫的事但現(xiàn)在不急迫的話就會(huì)從fastQueue分到slowQueue中。并且當(dāng)fastQueue在工作時(shí),slowQueue是處于掛起狀態(tài)的。
這讓那些急需顯示的圖片可以單獨(dú)訪問網(wǎng)絡(luò),同時(shí)也確保了一張非急需顯示的圖片可以在過一會(huì)成為一張急需顯示的圖片,因?yàn)樗呀?jīng)存到了緩存當(dāng)中,隨時(shí)準(zhǔn)備用于顯示。
重點(diǎn) 3: 盡可能簡單的API
大多數(shù)庫都做到了這一點(diǎn)。許多庫為了隱藏細(xì)節(jié)內(nèi)容而提供了UIImageView的分類,并且許多庫讓抓取一張圖片的流程變得盡可能的便利。針對我們經(jīng)常做的三件事,我們的庫選定了三個(gè)主要的方法:
快速抓到一張圖
__weak SGEventCell *me = self;[SGImageCache getImageForURL:event.imageURL thenDo:^(UIImage *image) { me.imageView.image = image;}];
排隊(duì)等待一張我們一會(huì)才需要的圖片
[SGImageCache slowGetImageForURL:event.imageURL thenDo:nil];
通知緩存一張急需顯示的圖已經(jīng)不需要立刻顯示
[SGImageCache moveTaskToSlowQueueForURL:event.imageURL];
結(jié)論
通過專注于預(yù)取,隊(duì)列管理,從主線程移除耗時(shí)的任務(wù),并且依賴于UIImage內(nèi)置的內(nèi)存緩存,我們努力從一個(gè)簡單的軟件包中得到好的結(jié)果。
相關(guān)文章
iOS使用UIScrollView實(shí)現(xiàn)無限循環(huán)輪播圖效果
這篇文章主要介紹了iOS使用UIScrollView實(shí)現(xiàn)無限循環(huán)輪播圖效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07Swift中的HTTP請求體Request Bodies使用示例詳解
這篇文章主要為大家介紹了Swift中的HTTP請求體Request Bodies使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02IOS開發(fā)之適配iOS10及Xcode8的注意點(diǎn)
這篇文章主要介紹了IOS開發(fā)之適配iOS10及Xcode8的注意點(diǎn),本文給大家介紹了可能出現(xiàn)的問題及相應(yīng)的解決方法,非常不錯(cuò)具有參考借鑒價(jià)值,感興趣的朋友一起看看2016-10-10iOS動(dòng)態(tài)調(diào)整UILabel高度的幾種方法
在iOS編程中UILabel是一個(gè)常用的控件,下面這篇文章主要給大家介紹了關(guān)于iOS動(dòng)態(tài)調(diào)整UILabel高度的幾種方法,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12ios7中UIViewControllerBasedStatusBarAppearance作用詳解
這篇文章主要介紹了 ios7中UIViewControllerBasedStatusBarAppearance作用詳解的相關(guān)資料,需要的朋友可以參考下2016-11-11iOS使用NSURLConnection實(shí)現(xiàn)斷點(diǎn)續(xù)傳下載
這篇文章主要為大家詳細(xì)介紹了iOS使用NSURLConnection實(shí)現(xiàn)斷點(diǎn)續(xù)傳下載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04