咱們的 iOS 應用都包含了大量的圖像。建立富有吸引力的視圖,主要依賴於大量的裝飾圖片,全部這些首先必須從遠程服務器獲取。若是每次打開應用都要從服務器一次又一次的獲取每一個圖像,那麼用戶體驗確定達不到好的效果,因此本地緩存遠程圖像是很是有必要的。linux |
葉秀蘭
|
版本1-尋找一張圖片,並從磁盤上讀取它咱們的第一個圖片緩存是簡單可是有效的。從緩存中尋找每個咱們曾經訪問過的圖片,用遠程的URL做爲緩存的鍵值。若是本地的磁盤緩存時有效的,從磁盤中讀取文件並建立UIImage,並當即返回。若是再磁盤上沒有找到文件,異步的從遠程URL獲取文件,緩存到磁盤,而後返回一個新建的UIImage。緩存 目前這對於咱們的使用是徹底知足了。可是它有一個沒必要要的弱點:每次緩存請求都須要從磁盤讀取圖片,性能都消耗在對磁盤的訪問和圖片文件的解碼上了。服務器 |
地獄星星
|
版本 2 - 內存緩存謝天謝地,蘋果的UIImage有內置的內存緩存。因此,你只需修改一行代碼,圖片就能從磁盤緩存改成內存緩存。性能 當你使用imageNamed:獲取UIImage的時候,它第一步就是檢查本身的內存看看是否已經加載過這個圖片。若是這樣,你不用任何系統花銷就能獲得一個UIImage實例。因此把本來這樣的寫法替換掉:url return [UIImage imageWithContentsOfFile:[self absolutePathForURL:url]]; 咱們能夠零花銷去訪問內存緩存,用下面的代碼就能夠了:.net return [UIImage imageNamed:[self relativePathForURL:url]]; UIImage 會查找它的內存緩存,若是找到,就會零消耗地返回這個照片. 若是沒有找到,這張照片就會從磁盤加載,消耗必定的系統性能。 |
美其名曰
|
版本3 —— 獲取隊列、數據預取和變量催促當改進應用程序設計時咱們渴望更多的圖片元素,渴望更炫的畫面,更大的圖片,更多的圖片。 讓這些大圖儘量快的顯示在屏幕上對用戶體驗來講是相當重要的,而只是簡單地在每次須要顯示的時候去緩存當中抓取圖片數據是不可以解決問題的。大圖須要更多的時間才能從網絡上加載回來,而且一次請求太多的圖片會致使全部圖片都來不及加載。須要仔細考慮何時去檢查緩衝當中有無圖片數據以及何時從網絡上抓取圖片。咱們須要預緩存和抓取列陣。 |
jiangguo
|
快速隊列和慢速隊列 咱們設置了兩個隊列,一個串行,一個並行。在屏幕上被迫切要求的圖片進入並行隊列(fastQueue),可能晚點才須要的圖片進入串行隊列(slowQueue)。 就UITableView的實現而言,這意味着在屏幕上的表格單元從fastQueue獲取圖片, 每一個關閉的屏幕行的圖片從slowQueue預加載。 |
地獄星星
|
如今不須要處理圖片假設咱們要從服務器上請求包含30條事件的一頁資訊回來,一旦這些內容請求回來時咱們就能夠排隊等待預取其中的每一張圖。 - (void)pageLoaded:(NSArray *)newEvents { for (SGEvent *event in newEvents) { [SGImageCache slowGetImageForURL:event.imageURL thenDo:nil]; } } slowGetImageForURL:這個方法將圖片添加到slowQueue這個隊列當中,容許它們在不阻塞網絡通訊的前提下被一張一張的取出來。 thenDo:這個代碼塊在這裏是沒有被實現,是由於咱們目前還不須要對圖片作任何事情。全部咱們須要作的就是確保它們在本地磁盤緩存當中,而且隨時準備在屏幕上滑動表格時來使用。 |
jiangguo
|
如今就要處理圖片顯示在屏幕上的表格但願當即顯示它們的圖片,因此在table cell子類當中實現: - (void)setEvent:(SGEvent *)event { __weak SGEventCell *me = self; [SGImageCache getImageForURL:event.imageURL thenDo:^(UIImage *image) { me.imageView.image = image; } ]; } getImageForURL:這個方法將抓取圖片的過程添加到fastQueue這個隊列當中,意味着只要iOS系統容許,它們會並行被地執行。若是抓取圖片的過程已經存在於slowQueue隊列當中,它會被移動到fastQueue隊列中,從而避免重複請求。 |
jiangguo
|
一直異步等等,getImageForURL:不是一個異步方法嗎?若是你明知道圖片已經在緩存中,可是卻不想在主線程上當即使用它嗎?直覺告訴你那是錯誤的。 從磁盤上加載圖片太費資源,一樣解壓圖片也會費不少資源。能夠在滑動的過程中進行配置和添加表格,這最後一件你想在滑動表格時作的事是很危險地,由於它會阻塞主線程,會有卡頓的現象出現。 使用getImageForURL:可讓磁盤加載的動做脫離主線程,因而當thenDo:這個用於收尾工做的代碼塊執行的時候它已經有了一個UIImage實例,從而不會有滑動卡頓的危險。若是圖片已經存在於本地緩存當中,用於收尾工做的代碼塊會在下一次運行週期執行,而且用戶不會注意到二者之間的差異。他們會注意到的是滑動不會卡頓了。 |
jiangguo
|
如今,不須要你快速執行若是用戶很快的滑動表格到底部,幾十或幾百個表格單元會出如今屏幕上,並向fastQueue請求圖片數據,而後很快地從屏幕上消失。忽然間這個並行地隊列會將大量實際上再也不須要的圖片請求充斥進網絡。當用戶最終中止滑動時,那些當前屏幕上相應的表格單元視圖會將它們的圖片請求至於那些並不急需的請求後面,所以網絡阻塞了。 這就是 wheremoveTaskToSlowQueueForURL:這個方法的產生的緣由. // 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中的只會有真正須要被快速執行的任務。任何之前認爲須要快速執行但如今不須要的任務會被移至slowQueue中。 |
jiangguo
|
重點和選擇已經有至關多的iOS圖片緩存庫。它們中一些庫只針對某些應用場景,一些庫提供了不一樣場景必定的可擴展性。咱們的庫即沒有專門針對某些應用場景,也沒有太多大而全的特性。針對咱們的用戶咱們有三類基本的重點: 重點 1: 最好的幀率不少的庫都很是專一在這一點上,使用一些高度定製和複雜的方法,儘管基準沒有決定性地顯示這樣有效。咱們發現最好的幀率由這些決定:
|
jiangguo
|
重點 2: 讓最最重要的圖片優先顯示大多數的庫都考慮讓隊列管理成爲別人關心的事。對於咱們的應用,這幾乎是最重要的點。 讓正確的圖片在正確的時間顯示在屏幕上能夠歸結爲一個簡單的問題:「咱們如今就須要它顯示仍是過一下子?」。那些須要當即顯示的圖片是並行加載地,而其它全部東西都被添加到串行隊列中。全部以前急迫的事但如今不急迫的話就會從fastQueue分到slowQueue中。而且當fastQueue在工做時,slowQueue是處於掛起狀態的。 這讓那些急需顯示的圖片能夠單獨訪問網絡,同時也確保了一張非急需顯示的圖片能夠在過一會成爲一張急需顯示的圖片,由於它已經存到了緩存當中,隨時準備用於顯示。 |
jiangguo
|
重點 3: 儘量簡單的API大多數庫都作到了這一點。許多庫爲了隱藏細節內容而提供了UIImageView的分類,而且許多庫讓抓取一張圖片的流程變得儘量的便利。針對咱們常常作的三件事,咱們的庫選定了三個主要的方法: 快速抓到一張圖 __weak SGEventCell *me = self;[SGImageCache getImageForURL:event.imageURL thenDo:^(UIImage *image) { me.imageView.image = image;}]; 排隊等待一張咱們一會才須要的圖片 [SGImageCache slowGetImageForURL:event.imageURL thenDo:nil]; 通知緩存一張急需顯示的圖已經不須要馬上顯示 [SGImageCache moveTaskToSlowQueueForURL:event.imageURL]; 結論經過專一於預取,隊列管理,從主線程移除耗時的任務,而且依賴於UIImage內置的內存緩存,咱們努力從一個簡單的軟件包中獲得好的結果。 一些有用的連接 |