梳理一下,在開發中利用SDWebImage下載圖片 ,這個框架會幫我們做什么事情。
這里自己寫代碼來實現解決所有的問題。
項目準備:
-
1.首先創建數據源數組
@implementation ViewController { /// 數據源數組 NSArray *_appsList; } -(void)viewDidLoad { [super viewDidLoad]; [self loadJsonData]; }
-
2.利用第三方框架
AFNetworking
獲取網絡數據///定義獲取JSON的主方法 -(void)loadJsonData{ //1、創建網絡請求管理者 AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; //2、獲取 [manager GET:@"https://raw.githubusercontent.com/lcy237777480/FYLoadImage/ master/apps.json" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, NSArray * responseObject) { //請求網絡執行 回調(成功/失敗)都是在主線程 NSLog(@"%@ %@ \n %@",[responseObject class],responseObject,[NSThread currentThread]); //responseObject就是獲取到的json數據 //1、遍歷數據數組字典轉模型 //4、創建可變數組用來保存模型 NSMutableArray *modelsArr = [NSMutableArray arrayWithCapacity:responseObject.count]; [responseObject enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { //2、創建模型類 //3、賦值 FYAppModel *model = [FYAppModel appWithDict:obj]; [modelsArr addObject:model]; }]; _appsArrM = modelsArr.copy; //網絡請求是耗時操作,拿到數據一定要reloadData [self.tableView reloadData]; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { }]; }
-
3.實現tableView的數據源方法
#pragma mark - 數據源方法 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return _appsArrM.count; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"reuseCellID" forIndexPath:indexPath]; FYAppModel *model = _appsArrM[indexPath.row]; // 給cell的子控件賦值 cell.textLabel.text = model.name; cell.detailTextLabel.text = model.download; //利用SDWebImage框架 下載圖片 [cell.imageView sd_setImageWithURL:[NSURL URLWithString:model.icon]]; return cell; }
項目準備完畢
接下來的實現不再用SDWebImage,自己實現NSBlockOperation異步下載圖片,看看我們遇到了什么問題,也就是他幫助我們做了什么。
增加全局隊列
@implementation ViewController {
/// 數據源數組
NSArray *_appsList;
/// 全局隊列
NSOperationQueue *_queue;
}
實例化隊列
-(void)viewDidLoad {
[super viewDidLoad];
// 實例化隊列
_queue = [[NSOperationQueue alloc] init];
[self loadJsonData];
}
問題1 : 列表顯示出來后,并不顯示圖片,來回滾動cell或者點擊cell ,圖片才會顯示。

不顯示圖片
解決辦法 : 自定義cell
修改數據源方法
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
APPCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppsCell" forIndexPath:indexPath];
// 獲取cell對應的數據模型
AppsModel *app = _appsList[indexPath.row];
// 給cell傳入模型對象
cell.app = app;
#**pragma mark - NSBlockOperation實現圖片的異步下載**
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 模擬網絡延遲
[NSThread sleepForTimeInterval:0.2];
// URL
NSURL *URL = [NSURL URLWithString:app.icon];
// data
NSData *data = [NSData dataWithContentsOfURL:URL];
// image
UIImage *image = [UIImage imageWithData:data];
// 圖片下載完成之后,回到主線程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.iconImageView.image = image;
}];
}];
// 把操作添加到隊列
[_queue addOperation:op];
return cell;
}
原理:
1.cell上的系統默認的子控件都是懶加載上去的
2.在返回cell之前,如果沒有給cell上的默認的子控件賦值,那么這個默認的子控件就不會加載到cell上;
3.跟cell做交互(點擊)時,默認會自動調用layoutSubViews方法,重新布局了子控件。
問題2 : 當有網絡延遲時,來回滾動cell,會出現cell上圖片的閃動;因為cell有復用

圖片的閃動
解決辦法 : 占位圖
// 在圖片下載之前,先設置占位圖
cell.iconImageView.image = [UIImage imageNamed:@"user_default"];

添加站位圖后
問題3 : 圖片每次展示,都要重新下載,用戶流量流失快
解決辦法 : 設計內存緩存策略 (字典)
-
3.1增加圖片緩存池
@implementation ViewController { /// 數據源數組 NSArray *_appsList; /// 全局隊列 NSOperationQueue *_queue; /// 圖片緩存池 NSMutableDictionary *_imagesCache; }
-
3.2實例化圖片緩存池
-(void)viewDidLoad { [super viewDidLoad]; // 實例化隊列 _queue = [[NSOperationQueue alloc] init]; // 實例化圖片緩存池 _imagesCache = [[NSMutableDictionary alloc] init]; [self loadJsonData]; }
-
3.3 在cell的數據源方法中向緩存池中獲取圖片
// 在建立下載操作之前,判斷要下載的圖片在圖片緩存池里面有沒有 UIImage *memImage = [_imagesCache objectForKey:app.icon]; //如果獲取到圖片 if (memImage) { NSLog(@"從內存中加載...%@",app.name); //賦值 cell.iconImageView.image = memImage; //直接返回,不執行后續操作 return cell; }
-
3.4在異步下載圖片的時候,將下載的圖片放入圖片緩存池中
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"從網絡中加載...%@",app.name); // 模擬網絡延遲 [NSThread sleepForTimeInterval:0.2]; // URL NSURL *URL = [NSURL URLWithString:app.icon]; // data NSData *data = [NSData dataWithContentsOfURL:URL]; // image UIImage *image = [UIImage imageWithData:data]; // 圖片下載完成之后,回到主線程更新UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ cell.iconImageView.image = image; // 把圖片保存到圖片緩存池 if (image != nil) { [_imagesCache setObject:image forKey:app.icon]; } }]; }];

建立圖片緩存,避免重復下載
問題4 : 當有網絡延遲時,滾動cell會出現圖片錯行的問題

cell錯行
解決辦法 : 刷新對應的行
// 圖片異步下載完成之后,刷新對應的行,并且不要動畫(偷偷的,不讓用戶發現)
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
問題5 : 當有網絡延遲時,來回滾動cell,會重復建立下載操作
[站外圖片上傳中……(6)]
解決辦法 : 操作緩存池 (字典)
-
5.1建立操作緩存池
@implementation ViewController { /// 數據源數組 NSArray *_appsList; /// 全局隊列 NSOperationQueue *_queue; /// 圖片緩存池 NSMutableDictionary *_imagesCache; /// 操作緩存池 NSMutableDictionary *_OPCache; }
-
5.2實例化
_OPCache = [[NSMutableDictionary alloc] init];
-
5.3模擬網絡延遲
// 在建立下載操作之前,判斷下載操作是否存在 if ([_OPCache objectForKey:app.icon] != nil) { NSLog(@"正在下載中...%@",app.name); return cell; } NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ *************************************** // 模擬網絡延遲 : 讓屏幕之外的圖片的下載延遲時間比較長 if (indexPath.row > 9) { [NSThread sleepForTimeInterval:15.0]; } *************************************** NSURL *URL = [NSURL URLWithString:app.icon]; NSData *data = [NSData dataWithContentsOfURL:URL]; UIImage *image = [UIImage imageWithData:data]; // 圖片下載完成之后,回到主線程更新UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ if (image != nil) { [_imagesCache setObject:image forKey:app.icon]; [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; } *************************************** // 圖片下載完成之后,需要把操作緩存池的操作移除 [_OPCache removeObjectForKey:app.icon]; *************************************** }]; }]; *************************************** // 把下載操作添加到操作緩存池 [_OPCache setObject:op forKey:app.icon]; *************************************** // 把操作添加到隊列 [_queue addOperation:op]; return cell; }
[站外圖片上傳中……(7)]
問題6 : 處理內存警告
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// 清除圖片緩存池
[_imagesCache removeAllObjects];
// 清除操作緩存池
[_OPCache removeAllObjects];
// 清除隊列里面所有的操作
[_queue cancelAllOperations];
}
問題7 : 當程序再次啟動時,內存緩存失效了;要設計沙盒緩存策略
-
7.1在cell數據源方法中
// 在建立下載操作之前,內存緩存判斷之后,判斷沙盒緩存 UIImage *cacheImage = [UIImage imageWithContentsOfFile:[app.icon appendCachesPath]]; if (cacheImage) { NSLog(@"從沙盒中加載...%@",app.name); // 在內存緩存保存一份 [_imagesCache setObject:cacheImage forKey:app.icon]; // 賦值 cell.iconImageView.image = cacheImage; return cell; }
-
7.2 創建了一個字符串的分類,獲取沙盒圖片緩存路徑
- (NSString *)appendCachesPath { // 獲取沙盒路徑 NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask, YES).lastObject; // 獲取文件名 : //如http://p16.qhimg.com/dr/48_48_/t0125e8d438ae9d2fbb.png // self : 這個方法的調用者 // lastPathComponent : 截取網絡地址最后一個`/`后面的內容(就是圖片名) NSString *name = [self lastPathComponent]; // 路徑拼接文件名 // stringByAppendingPathComponent : 會自動添加`/` NSString *filePath = [path stringByAppendingPathComponent:name]; return filePath; }
搞定
[站外圖片上傳中……(8)]