當前位置:
首頁 > 最新 > iOS圖片載入策略的簡單實現

iOS圖片載入策略的簡單實現

今天和大家一起來討論如何進行iOS圖片載入策略的簡單實現,有疏忽的地方,還望各位不吝賜教。

一、不自量力的說明

對於iOS圖片載入策略的實現,相信大家和我一樣更多的還是藉助於第三方,我在此班門弄斧的意義是應付一些公司的面試,嘗試以一種簡單的方式去實現圖片載入策略,藉此也說明一些其他方面的知識。在此我將使用一個TableView的例子進行說明,採用的是MVC的設計模式。如果採用的是Swift編寫,還請給位大神自行轉換。

二、邏輯敘述

這個邏輯的流程是我隨手畫的,只做參考。

邏輯實現.png

三、實現過程 -- 以多圖下載為例

1、先上一個簡單粗暴的實現。

// cellForRowAtIndexPath:方法中的實現過程

// 1、設置cell的重用標識

static NSString *cellID = @"app";

// 2、創建cell

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];

// 3、設置cell的數據 AppItem是模型

AppItem *item = self.apps[indexPath.row];

// 4、設置標題

cell.textLabel.text = item.name;

// 5、設置子標題

cell.detailTextLabel.text = item.download;

// 6、設置圖標

NSURL *url = [NSURL URLWithString:item.icon];

NSData *imageData = [NSData dataWithContentsOfURL:url];

UIImage *image = [UIImage imageWithData:imageData];

cell.imageView.image = image;

NSLog(@"%zd----",indexPath.row);

2、以上的方式實現的問題

圖片重複下載,通過添加的列印可以很明顯的看出來,解決方式是把之前下載好的圖片保存起來,因為圖片和文字要對應,所以採用字典的方式進行存儲。

1、添加內存緩存解決圖片重複下載問題

// 針對圖片重複下載的問題

// 1、創建一個NSMutableDictionary屬性來保存圖片

/** 內存緩存 */

@property (nonatomic, strong) NSMutableDictionary *images;

// 2、懶載入實現

- (NSMutableDictionary *)images{

if (!_images) {

_images = [NSMutableDictionary dictionary];

}

return _images;

}

// 3、設置圖標改造

// 先去查看內存緩存中該圖片有沒有被下載過,如果有直接拿來用,如果沒有再下載

// 設置圖片,否則直接下載

UIImage *image = [self.images objectForKey:item.icon];

// 如果有值直接拿來用

if(image){

cell.imageView.image = image;

NSLog(@"使用了內存緩存中的圖片%zd----",indexPath.row);

}else{

NSURL *url = [NSURL URLWithString:item.icon];

NSData *imageData = [NSData dataWithContentsOfURL:url];

UIImage *image = [UIImage imageWithData:imageData];

cell.imageView.image = image;

// 保存圖片到內存緩存 用圖片的URL作為圖片的key 保證key唯一

[self.images setObject:image forKey:item.icon];

NSLog(@"下載了圖片%zd----",indexPath.row);

}

2、添加內存緩存存在問題,需要用磁碟緩存(沙盒緩存)來補充

/*

* 兩種情況 圖片沒有下載和應用關閉都要考慮

* 只是添加內存緩存在程序退出的時候內存緩存會被釋放,接著優化

*/

/* 沙盒緩存的相關概念

document :會備份,蘋果官方不允許將緩存放到這裡,上架被拒絕

library:

preference:偏好設置,存放賬號密碼等數據

cache: 保存緩存文件,不會被備份

temp:臨時路徑(隨時有可能被刪除)

*/

// 先去查看內存緩存中該圖片有沒有被下載過,如果有直接拿來用,如果沒有就去檢查磁碟緩存,如果沒有磁碟緩存,就保存一份到內存,設置圖片,否則就直接下載

UIImage *image = [self.images objectForKey:item.icon];

// 如果有值直接拿來用

if(image){

cell.imageView.image = image;

NSLog(@"使用了內存緩存中的圖片----%zd",indexPath.row);

}else{

// 沙盒緩存路徑獲取(磁碟緩存)

NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

// 獲得圖片的名稱

NSString *imageName = [item.icon lastPathComponent];

// 拼接全路徑

NSString *fullPath = [cachePath stringByAppendingPathComponent:imageName];

// 檢查磁碟緩存

NSData *imageData = [NSData dataWithContentsOfFile:fullPath];

if (imageData) {

// 設置圖標

UIImage *image = [UIImage imageWithData:imageData];

cell.imageView.image = image;

NSLog(@"沙盒存儲----%zd",indexPath.row);

// 保存圖片到內存緩存

[self.images setObject:image forKey:item.icon];

}else{

NSURL *url = [NSURL URLWithString:item.icon];

NSData *imageData = [NSData dataWithContentsOfURL:url];

UIImage *image = [UIImage imageWithData:imageData];

cell.imageView.image = image;

// 保存圖片到內存緩存 用圖片的URL作為圖片的key 保證key唯一

[self.images setObject:image forKey:item.icon];

// 保存圖片到沙盒緩存(磁碟緩存)

[imageData writeToFile:fullPath atomically:YES];

NSLog(@"下載了圖片----%zd",indexPath.row);

}

}

UI不流暢,因為下載圖片操作和刷新UI的操作都是在主線程中操作的,解決方法把下載操作放到子線程中進行操作。

/*

* 改造下載圖片的部分,下載圖片放到子線程中去做,刷新UI放在主線程中做。

*/

/** 並發隊列 使用NSOperation為了防止重複創建隊列,設置全局屬性*/

@property (nonatomic, strong) NSOperationQueue *queue;

// 並發隊列懶載入

- (NSOperationQueue *)queue{

if (!_queue) {

_queue = [[NSOperationQueue alloc] init];

// 設置最大並發數

_queue.maxConcurrentOperationCount = 5;

}

return _queue;

}

// 設置圖標的位置修改如下:

// 先去查看內存緩存中該圖片有沒有被下載過,如果有直接拿來用,如果沒有就去檢查磁碟緩存,如果沒有磁碟緩存,就保存一份到內存,設置圖片,否則就直接下載。

UIImage *image = [self.images objectForKey:item.icon];

// 如果有值直接拿來直接使用

if(image){

cell.imageView.image = image;

NSLog(@"使用了內存緩存中的圖片----%zd",indexPath.row);

}else{

// 沙盒緩存路徑獲取(磁碟緩存)

NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

// 獲得圖片的名稱

NSString *imageName = [item.icon lastPathComponent];

// 拼接全路徑

NSString *fullPath = [cachePath stringByAppendingPathComponent:imageName];

// 檢查磁碟緩存

NSData *imageData = [NSData dataWithContentsOfFile:fullPath];

if (imageData) {

// 設置圖標

UIImage *image = [UIImage imageWithData:imageData];

cell.imageView.image = image;

NSLog(@"沙盒存儲----%zd",indexPath.row);

// 保存圖片到內存緩存

[self.images setObject:image forKey:item.icon];

}else{

// 創建操作

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{

NSURL *url = [NSURL URLWithString:item.icon];

NSData *imageData = [NSData dataWithContentsOfURL:url];

UIImage *image = [UIImage imageWithData:imageData];

NSLog(@"下載------%@",[NSThread currentThread]);

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

cell.imageView.image = image;

NSLog(@"UI------%@",[NSThread currentThread]);

}];

// 保存圖片到內存緩存 用圖片的URL作為圖片的key 保證key唯一

[self.images setObject:image forKey:item.icon];

// 保存圖片到沙盒緩存(磁碟緩存)

[imageData writeToFile:fullPath atomically:YES];

NSLog(@"下載了圖片%zd----",indexPath.row);

}];

// 添加下載操作到並發隊列中

[self.queue addOperation:blockOperation];

}

}

3、進行了2以後產生的新問題

UI不會自動刷新,當我拖動的時候才會刷新頁面,解決方式要使用代碼手動刷新。因為下載操作是非同步的,會先把沒有圖標的cell返回,此時因為圖標的frame為0,所以之後即使圖片下載下來,frame變為其他額尺寸,frame還是會一直為0,所以不會顯示。如果我手動刷新,系統會從新走一遍cellForRowAtIndexPath:方法,到設置圖標這一步時,會直接把內存中存在的圖片(此時圖片是有frame的)直接設置到cell中會顯示。

/*

* 改造下載圖片的部分,下載圖片放到子線程中去做,刷新UI放在主線程中做。

*/

// 設置圖標的位置修改如下:

// 先去查看內存緩存中該圖片有沒有被下載過,如果有直接拿來用,如果沒有就去檢查磁碟緩存,如果沒有磁碟緩存,就保存一份到內存,設置圖片,否則就直接下載。

UIImage *image = [self.images objectForKey:item.icon];

// 如果有值直接拿來直接使用

if(image){

cell.imageView.image = image;

NSLog(@"使用了內存緩存中的圖片----%zd",indexPath.row);

}else{

// 沙盒緩存路徑獲取(磁碟緩存)

NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

// 獲得圖片的名稱

NSString *imageName = [item.icon lastPathComponent];

// 拼接全路徑

NSString *fullPath = [cachePath stringByAppendingPathComponent:imageName];

// 檢查磁碟緩存

NSData *imageData = [NSData dataWithContentsOfFile:fullPath];

if (imageData) {

// 設置圖標

UIImage *image = [UIImage imageWithData:imageData];

cell.imageView.image = image;

NSLog(@"沙盒存儲----%zd",indexPath.row);

// 保存圖片到內存緩存

[self.images setObject:image forKey:item.icon];

}else{

// 創建下載操作

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{

NSURL *url = [NSURL URLWithString:item.icon];

NSData *imageData = [NSData dataWithContentsOfURL:url];

UIImage *image = [UIImage imageWithData:imageData];

NSLog(@"下載------%@",[NSThread currentThread]);

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

cell.imageView.image = image;

NSLog(@"UI------%@",[NSThread currentThread]);

// 手動刷新 刷新UITableView指定的行

[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];

}];

// 保存圖片到內存緩存 用圖片的URL作為圖片的key 保證key唯一

[self.images setObject:image forKey:item.icon];

// 保存圖片到沙盒緩存(磁碟緩存)

[imageData writeToFile:fullPath atomically:YES];

NSLog(@"下載了圖片%zd----",indexPath.row);

}];

// 添加下載操作到並發隊列中

[self.queue addOperation:blockOperation];

}

}

由於拖動太快,導致的重複下載的問題。解決方式是定義一個操作緩存(字典),把之前的操作都保存起來,因為上面出現的原因本質就是因為blockOperation重複添加到queue中了。這個問題是這樣的,需要顯示的圖片會進行下載操作,但是一張圖片下載需要時間,如果圖片還沒下載下來,我就拖動了,將它移出屏幕,結果就是這張圖片沒有下載完,如果這時候我又拖動了,又要顯示這張圖片,因為上一次還沒下載完,所以內存和磁碟里都沒有,所以會重複下載。解決方式是定義一個操作緩存(字典),把之前的操作都保存起來,因為上面出現的原因本質就是因為blockOperation重複添加到queue中了。

/** 定義操作緩存屬性 */

@property (nonatomic, strong) NSMutableDictionary *operations;

// 操作緩存屬性懶載入實現

- (NSMutableDictionary *)operations{

if (!_operations) {

_operations = [NSMutableDictionary dictionary];

}

return _operations;

}

// 設置圖標的位置修改如下:

// 先去查看內存緩存中該圖片有沒有被下載過,如果有直接拿來用,如果沒有就去檢查磁碟緩存,如果沒有磁碟緩存,就保存一份到內存,設置圖片,否則就直接下載。

UIImage *image = [self.images objectForKey:item.icon];

// 如果有值直接拿來直接使用

if(image){

cell.imageView.image = image;

NSLog(@"使用了內存緩存中的圖片----%zd",indexPath.row);

}else{

// 沙盒緩存路徑獲取(磁碟緩存)

NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

// 獲得圖片的名稱

NSString *imageName = [item.icon lastPathComponent];

// 拼接全路徑

NSString *fullPath = [cachePath stringByAppendingPathComponent:imageName];

// 檢查磁碟緩存

NSData *imageData = [NSData dataWithContentsOfFile:fullPath];

if (imageData) {

// 設置圖標

UIImage *image = [UIImage imageWithData:imageData];

cell.imageView.image = image;

NSLog(@"沙盒存儲----%zd",indexPath.row);

// 保存圖片到內存緩存

[self.images setObject:image forKey:item.icon];

}else{

// 檢查圖片是否在操作緩存中進行下載,如果在下載就什麼也不做,如果不在就添加下載任務

NSBlockOperation *blockOperation = [self.operations objectForKey:item.icon];

if (blockOperation) {

}else{

// 創建下載操作

blockOperation = [NSBlockOperation blockOperationWithBlock:^{

NSURL *url = [NSURL URLWithString:item.icon];

NSData *imageData = [NSData dataWithContentsOfURL:url];

UIImage *image = [UIImage imageWithData:imageData];

NSLog(@"下載------%@",[NSThread currentThread]);

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

cell.imageView.image = image;

NSLog(@"UI------%@",[NSThread currentThread]);

// 手動刷新 刷新UITableView指定的行

[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];

}];

// 保存圖片到內存緩存 用圖片的URL作為圖片的key 保證key唯一

[self.images setObject:image forKey:item.icon];

// 保存圖片到沙盒緩存(磁碟緩存)

[imageData writeToFile:fullPath atomically:YES];

// 下載操作完成後進行移除操作

[self.operations removeObjectForKey:item.icon];

}];

// 添加下載操作到操作緩存中

[self.operations setObject:blockOperation forKey:item.icon];

// 添加下載操作到並發隊列中

[self.queue addOperation:blockOperation];

}

}

}

cell的重用機制導致的cell圖標展示數據錯亂的問題,解決方案,如果要進行下載圖片,先清空原來cell上的圖片,但是一般不會直接設置為nil,會採用占點陣圖片的方式來解決。

/** 定義操作緩存屬性 */

@property (nonatomic, strong) NSMutableDictionary *operations;

// 操作緩存屬性懶載入實現

- (NSMutableDictionary *)operations{

if (!_operations) {

_operations = [NSMutableDictionary dictionary];

}

return _operations;

}

// 設置圖標的位置修改如下:

// 先去查看內存緩存中該圖片有沒有被下載過,如果有直接拿來用,如果沒有就去檢查磁碟緩存,如果沒有磁碟緩存,就保存一份到內存,設置圖片,否則就直接下載。

UIImage *image = [self.images objectForKey:item.icon];

// 如果有值直接拿來直接使用

if(image){

cell.imageView.image = image;

NSLog(@"使用了內存緩存中的圖片----%zd",indexPath.row);

}else{

// 沙盒緩存路徑獲取(磁碟緩存)

NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

// 獲得圖片的名稱

NSString *imageName = [item.icon lastPathComponent];

// 拼接全路徑

NSString *fullPath = [cachePath stringByAppendingPathComponent:imageName];

// 檢查磁碟緩存

NSData *imageData = [NSData dataWithContentsOfFile:fullPath];

if (imageData) {

// 設置圖標

UIImage *image = [UIImage imageWithData:imageData];

cell.imageView.image = image;

NSLog(@"沙盒存儲----%zd",indexPath.row);

// 保存圖片到內存緩存

[self.images setObject:image forKey:item.icon];

}else{

// 檢查圖片是否在操作緩存中進行下載,如果在下載就什麼也不做,如果不在就添加下載任務

NSBlockOperation *blockOperation = [self.operations objectForKey:item.icon];

if (blockOperation) {

}else{

// 防止cell重用導致的數據錯亂 先設置cell 的 image為空

cell.imageView.image = [UIImage imageNamed:@"placeHolder.png"];

// 創建下載操作

blockOperation = [NSBlockOperation blockOperationWithBlock:^{

NSURL *url = [NSURL URLWithString:item.icon];

NSData *imageData = [NSData dataWithContentsOfURL:url];

UIImage *image = [UIImage imageWithData:imageData];

NSLog(@"下載------%@",[NSThread currentThread]);

// 當url地址不正確 image為空 容錯處理

if (!image) {

// 為了下一次進來的時候再次嘗試進行圖片下載

[self.operations removeObjectForKey:item.icon];

return ;

}

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

cell.imageView.image = image;

NSLog(@"UI------%@",[NSThread currentThread]);

// 手動刷新 刷新UITableView指定的行

[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];

}];

// 保存圖片到內存緩存 用圖片的URL作為圖片的key 保證key唯一

[self.images setObject:image forKey:item.icon];

// 保存圖片到沙盒緩存(磁碟緩存)

[imageData writeToFile:fullPath atomically:YES];

// 下載操作完成後進行移除操作

[self.operations removeObjectForKey:item.icon];

}];

// 添加下載操作到操作緩存中

[self.operations setObject:blockOperation forKey:item.icon];

// 添加下載操作到並發隊列中

[self.queue addOperation:blockOperation];

}

}

}

內存問題:將圖片保存在內存中是很方便的事,圖片少的情況下肯定沒問題,但是圖片多了就會內存警告,要做一下處理。

- (void)didReceiveMemoryWarning{

// 移除內存緩存,這裡不會影響界面顯示,因為有強引用的關係。

[self.images removeAllObjects];

// 移除隊列中所有操作

[self.queue cancelAllOperations];

}

寫在最後的話:關於iOS圖片載入策略的知識今天就分享到這裡,關於iOS圖片載入策略實現方面的問題歡迎大家和我交流,共同進步,謝謝各位。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Cocoa開發者社區 的精彩文章:

iOS多線程全面解讀(四):鎖

TAG:Cocoa開發者社區 |