梳理一下,在開(kāi)發(fā)中利用SDWebImage下載圖片 蒲每,這個(gè)框架會(huì)幫我們做什么事情纷跛。
這里自己寫(xiě)代碼來(lái)實(shí)現(xiàn)解決所有的問(wèn)題。
項(xiàng)目準(zhǔn)備:
-
1.首先創(chuàng)建數(shù)據(jù)源數(shù)組
@implementation ViewController { /// 數(shù)據(jù)源數(shù)組 NSArray *_appsList; } -(void)viewDidLoad { [super viewDidLoad]; [self loadJsonData]; }
-
2.利用第三方框架
AFNetworking
獲取網(wǎng)絡(luò)數(shù)據(jù)///定義獲取JSON的主方法 -(void)loadJsonData{ //1邀杏、創(chuàng)建網(wǎng)絡(luò)請(qǐng)求管理者 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) { //請(qǐng)求網(wǎng)絡(luò)執(zhí)行 回調(diào)(成功/失敗)都是在主線程 NSLog(@"%@ %@ \n %@",[responseObject class],responseObject,[NSThread currentThread]); //responseObject就是獲取到的json數(shù)據(jù) //1望蜡、遍歷數(shù)據(jù)數(shù)組字典轉(zhuǎn)模型 //4唤崭、創(chuàng)建可變數(shù)組用來(lái)保存模型 NSMutableArray *modelsArr = [NSMutableArray arrayWithCapacity:responseObject.count]; [responseObject enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { //2、創(chuàng)建模型類(lèi) //3脖律、賦值 FYAppModel *model = [FYAppModel appWithDict:obj]; [modelsArr addObject:model]; }]; _appsArrM = modelsArr.copy; //網(wǎng)絡(luò)請(qǐng)求是耗時(shí)操作谢肾,拿到數(shù)據(jù)一定要reloadData [self.tableView reloadData]; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { }]; }
-
3.實(shí)現(xiàn)tableView的數(shù)據(jù)源方法
#pragma mark - 數(shù)據(jù)源方法 -(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; }
項(xiàng)目準(zhǔn)備完畢
接下來(lái)的實(shí)現(xiàn)不再用SDWebImage,自己實(shí)現(xiàn)NSBlockOperation異步下載圖片小泉,看看我們遇到了什么問(wèn)題芦疏,也就是他幫助我們做了什么。
增加全局隊(duì)列
@implementation ViewController {
/// 數(shù)據(jù)源數(shù)組
NSArray *_appsList;
/// 全局隊(duì)列
NSOperationQueue *_queue;
}
實(shí)例化隊(duì)列
-(void)viewDidLoad {
[super viewDidLoad];
// 實(shí)例化隊(duì)列
_queue = [[NSOperationQueue alloc] init];
[self loadJsonData];
}
問(wèn)題1 : 列表顯示出來(lái)后微姊,并不顯示圖片酸茴,來(lái)回滾動(dòng)cell或者點(diǎn)擊cell ,圖片才會(huì)顯示兢交。
![不顯示圖片](http://i2.buimg.com/567571/dca99ab107d2af13.gif)
解決辦法 : 自定義cell
修改數(shù)據(jù)源方法
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
APPCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppsCell" forIndexPath:indexPath];
// 獲取cell對(duì)應(yīng)的數(shù)據(jù)模型
AppsModel *app = _appsList[indexPath.row];
// 給cell傳入模型對(duì)象
cell.app = app;
#**pragma mark - NSBlockOperation實(shí)現(xiàn)圖片的異步下載**
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 模擬網(wǎng)絡(luò)延遲
[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;
}];
}];
// 把操作添加到隊(duì)列
[_queue addOperation:op];
return cell;
}
原理:
1.cell上的系統(tǒng)默認(rèn)的子控件都是懶加載上去的
2.在返回cell之前,如果沒(méi)有給cell上的默認(rèn)的子控件賦值,那么這個(gè)默認(rèn)的子控件就不會(huì)加載到cell上;
3.跟cell做交互(點(diǎn)擊)時(shí),默認(rèn)會(huì)自動(dòng)調(diào)用layoutSubViews方法,重新布局了子控件薪捍。
問(wèn)題2 : 當(dāng)有網(wǎng)絡(luò)延遲時(shí),來(lái)回滾動(dòng)cell,會(huì)出現(xiàn)cell上圖片的閃動(dòng);因?yàn)閏ell有復(fù)用
![圖片的閃動(dòng)](http://i1.buimg.com/567571/23d58f7b0892f45b.gif)
解決辦法 : 占位圖
// 在圖片下載之前,先設(shè)置占位圖
cell.iconImageView.image = [UIImage imageNamed:@"user_default"];
![添加站位圖后](http://i1.buimg.com/567571/350650ba5f421cf1.gif)
問(wèn)題3 : 圖片每次展示,都要重新下載,用戶流量流失快
解決辦法 : 設(shè)計(jì)內(nèi)存緩存策略 (字典)
-
3.1增加圖片緩存池
@implementation ViewController { /// 數(shù)據(jù)源數(shù)組 NSArray *_appsList; /// 全局隊(duì)列 NSOperationQueue *_queue; /// 圖片緩存池 NSMutableDictionary *_imagesCache; }
-
3.2實(shí)例化圖片緩存池
-(void)viewDidLoad { [super viewDidLoad]; // 實(shí)例化隊(duì)列 _queue = [[NSOperationQueue alloc] init]; // 實(shí)例化圖片緩存池 _imagesCache = [[NSMutableDictionary alloc] init]; [self loadJsonData]; }
-
3.3 在cell的數(shù)據(jù)源方法中向緩存池中獲取圖片
// 在建立下載操作之前,判斷要下載的圖片在圖片緩存池里面有沒(méi)有 UIImage *memImage = [_imagesCache objectForKey:app.icon]; //如果獲取到圖片 if (memImage) { NSLog(@"從內(nèi)存中加載...%@",app.name); //賦值 cell.iconImageView.image = memImage; //直接返回配喳,不執(zhí)行后續(xù)操作 return cell; }
-
3.4在異步下載圖片的時(shí)候酪穿,將下載的圖片放入圖片緩存池中
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"從網(wǎng)絡(luò)中加載...%@",app.name); // 模擬網(wǎng)絡(luò)延遲 [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]; } }]; }];
![建立圖片緩存,避免重復(fù)下載](http://i1.buimg.com/567571/6f99823c3dac6168.gif)
問(wèn)題4 : 當(dāng)有網(wǎng)絡(luò)延遲時(shí),滾動(dòng)cell會(huì)出現(xiàn)圖片錯(cuò)行的問(wèn)題
![cell錯(cuò)行](http://i4.buimg.com/567571/61b8dc2be79b16a0.gif)
解決辦法 : 刷新對(duì)應(yīng)的行
// 圖片異步下載完成之后,刷新對(duì)應(yīng)的行被济,并且不要?jiǎng)赢?huà)(偷偷的,不讓用戶發(fā)現(xiàn))
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
問(wèn)題5 : 當(dāng)有網(wǎng)絡(luò)延遲時(shí),來(lái)回滾動(dòng)cell,會(huì)重復(fù)建立下載操作
[站外圖片上傳中……(6)]
解決辦法 : 操作緩存池 (字典)
-
5.1建立操作緩存池
@implementation ViewController { /// 數(shù)據(jù)源數(shù)組 NSArray *_appsList; /// 全局隊(duì)列 NSOperationQueue *_queue; /// 圖片緩存池 NSMutableDictionary *_imagesCache; /// 操作緩存池 NSMutableDictionary *_OPCache; }
-
5.2實(shí)例化
_OPCache = [[NSMutableDictionary alloc] init];
-
5.3模擬網(wǎng)絡(luò)延遲
// 在建立下載操作之前,判斷下載操作是否存在 if ([_OPCache objectForKey:app.icon] != nil) { NSLog(@"正在下載中...%@",app.name); return cell; } NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ *************************************** // 模擬網(wǎng)絡(luò)延遲 : 讓屏幕之外的圖片的下載延遲時(shí)間比較長(zhǎng) 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]; *************************************** // 把操作添加到隊(duì)列 [_queue addOperation:op]; return cell; }
[站外圖片上傳中……(7)]
問(wèn)題6 : 處理內(nèi)存警告
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// 清除圖片緩存池
[_imagesCache removeAllObjects];
// 清除操作緩存池
[_OPCache removeAllObjects];
// 清除隊(duì)列里面所有的操作
[_queue cancelAllOperations];
}
問(wèn)題7 : 當(dāng)程序再次啟動(dòng)時(shí),內(nèi)存緩存失效了;要設(shè)計(jì)沙盒緩存策略
-
7.1在cell數(shù)據(jù)源方法中
// 在建立下載操作之前,內(nèi)存緩存判斷之后,判斷沙盒緩存 UIImage *cacheImage = [UIImage imageWithContentsOfFile:[app.icon appendCachesPath]]; if (cacheImage) { NSLog(@"從沙盒中加載...%@",app.name); // 在內(nèi)存緩存保存一份 [_imagesCache setObject:cacheImage forKey:app.icon]; // 賦值 cell.iconImageView.image = cacheImage; return cell; }
-
7.2 創(chuàng)建了一個(gè)字符串的分類(lèi)息拜,獲取沙盒圖片緩存路徑
- (NSString *)appendCachesPath { // 獲取沙盒路徑 NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask, YES).lastObject; // 獲取文件名 : //如http://p16.qhimg.com/dr/48_48_/t0125e8d438ae9d2fbb.png // self : 這個(gè)方法的調(diào)用者 // lastPathComponent : 截取網(wǎng)絡(luò)地址最后一個(gè)`/`后面的內(nèi)容(就是圖片名) NSString *name = [self lastPathComponent]; // 路徑拼接文件名 // stringByAppendingPathComponent : 會(huì)自動(dòng)添加`/` NSString *filePath = [path stringByAppendingPathComponent:name]; return filePath; }
搞定
[站外圖片上傳中……(8)]