@WilliamAlex大叔
前言
SDWebImage框架是我們最常用的框架,我們下載圖片,清除緩存等都可以使用該框架.下面我們模仿SDWebImage來實(shí)現(xiàn)多圖片的下載
在寫代碼之前,我們先整理整理思路,下載圖片我們分為兩種情況,有緩存和沒緩存,下面我們來看看我做的兩幅圖
-
無沙盒緩存
無沙盒緩存.png 有沙盒緩存
有沙盒緩存.png
- 糾正一點(diǎn): 圖片中的下載操作是是保存到一個(gè)可變的字典中的,不是數(shù)組,畫圖的時(shí)候一整天都沒有吃飯,肚子太餓了,出了錯(cuò),還望大家見諒
代碼實(shí)現(xiàn)
在正式寫代碼之前,需要說明幾步操作
- 定義模型類
- 將storyboard中的控制器移除,拖入一個(gè)新的UITableViewController控制器,綁定ID:"apps"以及綁定控制器,將類型設(shè)置為subtitle,最后給控制器設(shè)置啟動(dòng)箭頭.
- 將ViewController的父類換成UITableViewController
-
代碼存在很大問題(需要優(yōu)化)
- 問題 1,滑動(dòng)界面時(shí)有嚴(yán)重的卡頓現(xiàn)象,原因是下載操作是在主線程上執(zhí)行的.
#import "ViewController.h"
#import "WGApps.h"
@interface ViewController ()
/** 圖片緩存(plist文件中是字典存儲的) */
@property (nonatomic, weak) NSMutableDictionary *images;
/** apps數(shù)據(jù)源*/
@property(nonatomic, strong) NSArray *apps;
@end
@implementation ViewController
#pragma mark - 生命周期方法
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.rowHeight = 44;
}
#pragma mark ----------------
#pragma mark - lazyLoading
- (NSMutableDictionary *)images {
if (_images == nil) {
_images = [NSMutableDictionary dictionary];
}
return _images;
}
- (NSArray *)apps {
if (_apps == nil) {
// 加載plist文件
NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
// 加載數(shù)據(jù)
NSArray *dictArray = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:dictArray.count];
// 字典數(shù)組轉(zhuǎn)模型數(shù)組
for (NSDictionary *dict in dictArray) {
[tempArray addObject:[WGApps appsWithDict:dict]];
}
_apps = tempArray;
}
return _apps;
}
#pragma mark - 數(shù)據(jù)源方法
// 一共有多少個(gè)cell
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.apps.count;
}
// 每一個(gè)cell顯示什么內(nèi)容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 定義ID(最好和storyboard中定義的ID一致)
static NSString *ID = @"apps";
// 創(chuàng)建cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 通過模型拿到對應(yīng)的資源
WGApps *app = self.apps[indexPath.row];
// 設(shè)置數(shù)據(jù)
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
// 下載頭像
// 1, 首先去內(nèi)存緩存中取,如果沒有再去沙盒中去
UIImage *image = [self.images objectForKey:app.icon];
if (image) {
// 來到這里,說明圖片緩存中已經(jīng)有需要的圖片,直接顯示到對應(yīng)的cell即可
cell.imageView.image = image;
} else
{
// 來到這里表示:圖片緩存中沒有所需要的圖片,那么這時(shí)候就要到對應(yīng)的沙盒中找有沒有下載的圖片
// 1, 獲取沙盒路徑
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 注意:拿到沙盒路徑還不夠,我們需要的是下載圖片的路徑,所以需要拼接,拿到全路徑
// 2, 獲取模型中圖片資源最后一個(gè)目錄名
NSString *fileName = [app.icon lastPathComponent];
// 3, 拼接路徑
NSString *fullPath = [cachesPath stringByAppendingPathComponent:fileName];
// 注意 : 在沙盒中的保存的資源是以二進(jìn)制的形式存在的.我們還需要判斷沙盒中是否有下載過的圖片
// 4, 通過圖片路徑,拿到下載的圖片
NSData *imageData = [NSData dataWithContentsOfFile:fullPath];
// 5, 判斷沙盒中是否有值
if (imageData) {
// 來到這里說明沙盒中有下載好的圖片,直接將二進(jìn)制轉(zhuǎn)為圖片顯示即可,但是最后還需要講圖片報(bào)訊到圖片緩存中,方便下次直接獲取.
UIImage *image = [UIImage imageWithData:imageData];
// 顯示圖片
cell.imageView.image = image;
// 將圖片保存到圖片緩存中
[self.images setObject:image forKey:app.icon];
} else
{
// 來到這里說明沙盒中沒有值,這時(shí)候我們就需要下載圖片資源啦.
// 下載圖片
NSURL *url = [NSURL URLWithString:app.icon];
// 將url轉(zhuǎn)為data保存到本地
NSData *data = [NSData dataWithContentsOfURL:url];
// 再將二進(jìn)制轉(zhuǎn)為圖片顯示到cell上
UIImage *image = [UIImage imageWithData:data];
// 顯示圖片
cell.imageView.image = image;
// 將下載的圖片保存到圖片緩存和沙盒中
[self.images setObject:image forKey:app.icon];
[data writeToFile:fullPath atomically:YES];
}
}
return cell;
}
多圖片下載(優(yōu)化后的代碼)
- 解決的問題 : 解決了卡頓現(xiàn)象,避免了重復(fù)下載
#import "ViewController.h"
#import "WGApps.h"
@interface ViewController ()
/** 圖片緩存(plist文件中是字典存儲的) */
@property (nonatomic, weak) NSMutableDictionary *images;
/** apps數(shù)據(jù)源*/
@property(nonatomic, strong) NSArray *apps;
/** 下載操作 */
@property(nonatomic, strong) NSMutableDictionary *operations;
/** 隊(duì)列 */
@property(nonatomic, strong) NSOperationQueue *queue;
@end
@implementation ViewController
#pragma mark - 生命周期方法
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.rowHeight = 44;
}
#pragma mark ----------------
#pragma mark - lazyLoading
- (NSMutableDictionary *)operations
{
if (_operations == nil) {
_operations = [NSMutableDictionary dictionary];
}
return _operations;
}
- (NSOperationQueue *)queue {
if (_queue == nil) {
// 創(chuàng)建隊(duì)列
_queue = [[NSOperationQueue alloc] init];
// 設(shè)置最大并發(fā)數(shù)
_queue.maxConcurrentOperationCount = 3;
}
return _queue;
}
- (NSMutableDictionary *)images {
if (_images == nil) {
_images = [NSMutableDictionary dictionary];
}
return _images;
}
- (NSArray *)apps {
if (_apps == nil) {
// 加載plist文件
NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
// 加載數(shù)據(jù)
NSArray *dictArray = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:dictArray.count];
// 字典數(shù)組轉(zhuǎn)模型數(shù)組
for (NSDictionary *dict in dictArray) {
[tempArray addObject:[WGApps appsWithDict:dict]];
}
_apps = tempArray;
}
return _apps;
}
#pragma mark - 數(shù)據(jù)源方法
// 一共有多少個(gè)cell
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.apps.count;
}
// 每一個(gè)cell顯示什么內(nèi)容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 定義ID(最好和storyboard中定義的ID一致)
static NSString *ID = @"apps";
// 創(chuàng)建cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 通過模型拿到對應(yīng)的資源
WGApps *app = self.apps[indexPath.row];
// 設(shè)置數(shù)據(jù)
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
// 下載頭像
// 1, 首先去內(nèi)存緩存中取,如果沒有再去沙盒中去
UIImage *image = [self.images objectForKey:app.icon];
if (image) {
// 來到這里,說明圖片緩存中已經(jīng)有需要的圖片,直接顯示到對應(yīng)的cell即可
cell.imageView.image = image;
} else
{
// 來到這里表示:圖片緩存中沒有所需要的圖片,那么這時(shí)候就要到對應(yīng)的沙盒中找有沒有下載的圖片
// 1, 獲取沙盒路徑
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 注意:拿到沙盒路徑還不夠,我們需要的是下載圖片的路徑,所以需要拼接,拿到全路徑
// 2, 獲取模型中圖片資源最后一個(gè)目錄名
NSString *fileName = [app.icon lastPathComponent];
// 3, 拼接路徑
NSString *fullPath = [cachesPath stringByAppendingPathComponent:fileName];
// 注意 : 在沙盒中的保存的資源是以二進(jìn)制的形式存在的.我們還需要判斷沙盒中是否有下載過的圖片
// 4, 通過圖片路徑,拿到下載的圖片
NSData *imageData = [NSData dataWithContentsOfFile:fullPath];
// 5, 判斷沙盒中是否有值
if (imageData) {
// 來到這里說明沙盒中有下載好的圖片,直接將二進(jìn)制轉(zhuǎn)為圖片顯示即可,但是最后還需要講圖片報(bào)訊到圖片緩存中,方便下次直接獲取.
UIImage *image = [UIImage imageWithData:imageData];
// 顯示圖片
cell.imageView.image = image;
// 將圖片保存到圖片緩存中
[self.images setObject:image forKey:app.icon];
} else
{
//設(shè)置展占位圖片
cell.imageView.image = [UIImage imageNamed:@"占位圖片"];
//查看該圖片的下載操作是否存在
NSBlockOperation *download = [self.operations objectForKey:app.icon];
if (download == nil) {
download = [NSBlockOperation blockOperationWithBlock:^{
// 下載圖片
NSURL *url = [NSURL URLWithString:app.icon];
// 阻塞1秒
[NSThread sleepForTimeInterval:1.0];
// 將圖片轉(zhuǎn)為二進(jìn)制保存到本地沙盒中
NSData *data = [NSData dataWithContentsOfURL:url];
// 將二進(jìn)制轉(zhuǎn)為圖片顯示
UIImage *image = [UIImage imageWithData:data];
// 如果沒有圖片,一定要講下載操作從字典中移除
if (image == nil) {
[self.operations removeObjectForKey:app.icon];
return ;
}
//保存圖片到內(nèi)存緩存
[self.images setObject:image forKey:app.icon];
//保存圖片到沙河緩存
[data writeToFile:fullPath atomically:YES];
//線程間通信
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
//刷新cell
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}];
[self.operations removeObjectForKey:app.icon];
}];
//加入到操作緩存
[self.operations setObject:download forKey:app.icon];
//把操作添加到隊(duì)列
[self.queue addOperation:download];
}
}
}
return cell;
}
@end
我們使用框架來實(shí)現(xiàn)同樣功能
- 以上就是模仿SDWebImage內(nèi)部實(shí)現(xiàn)多圖片下載的原理,接下來我們使用SDWebImage來實(shí)現(xiàn)多圖片下載
#import "ViewController.h"
#import "WGApps.h"
#import "UIImageView+WebCache.h"
@interface ViewController ()
/** apps數(shù)據(jù)源*/
@property(nonatomic, strong) NSArray *apps;
@end
@implementation ViewController
#pragma mark - 生命周期方法
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.rowHeight = 84;
}
#pragma mark ----------------
#pragma mark - lazyLoading
- (NSArray *)apps {
if (_apps == nil) {
// 加載plist文件
NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
// 加載數(shù)據(jù)
NSArray *dictArray = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:dictArray.count];
// 字典數(shù)組轉(zhuǎn)模型數(shù)組
for (NSDictionary *dict in dictArray) {
[tempArray addObject:[WGApps appsWithDict:dict]];
}
_apps = tempArray;
}
return _apps;
}
#pragma mark - 數(shù)據(jù)源方法
// 一共有多少個(gè)cell
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.apps.count;
}
// 每一個(gè)cell顯示什么內(nèi)容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 定義ID(最好和storyboard中定義的ID一致)
static NSString *ID = @"apps";
// 創(chuàng)建cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 通過模型拿到對應(yīng)的資源
WGApps *app = self.apps[indexPath.row];
// 設(shè)置數(shù)據(jù)
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
// 下載圖片
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"placehoder"]];
return cell;
}
@end
總結(jié)
- 從上面的代碼來看,使用第三方框架大大減少了我們的工作量,不過在使用第三方框架時(shí),我們還是最好多了解一點(diǎn)框架的內(nèi)部原理,這樣即使出錯(cuò)了,我們很快就找到問題所在,就像上面優(yōu)化代碼時(shí),我遇到了一個(gè)bug : 我在拼接絕對路徑時(shí),使用錯(cuò)了方法,本應(yīng)該使用:stringByAppendingPathComponent.但是我的粗心使用了 : stringByAppendingString,導(dǎo)致出現(xiàn)了問題.經(jīng)過這樣去了解,這種bug我以后是不會再犯了,即使犯了,也會很快改正過來o