iOS学习

tableView加载网络图片

2016-04-28  本文已影响1155人  月下独酌灬

tableView加载网络图片

列表加载网络图片效果图.png 数据结构.png

获取模型数组

准备模型

.h文件

@interface AppInfo : NSObject

/// app名称
@property (nonatomic,copy) NSString *name;
/// app图像
@property (nonatomic,copy) NSString *icon;
/// app下载量
@property (nonatomic,copy) NSString *download;

/// 字典转模型
+ (instancetype)appInfoWithDict:(NSDictionary *)dict;

@end

.m文件

+ (instancetype)appInfoWithDict:(NSDictionary *)dict
{
    AppInfo *appInfo = [[AppInfo alloc] init];

    // 利用kvc将字典转换成模型 : 取出字典中key,对应的value,赋值给模型对应的属性
    [appInfo setValuesForKeysWithDictionary:dict];

    return appInfo;
}

控制器中获取模型数据

定义数据源数组

@interface ViewController ()

/// 数据源数组
@property (nonatomic,strong) NSArray *dataSourceArr;

@end

懒加载数据源数组

- (NSArray *)dataSourceArr
{
    if (_dataSourceArr==nil) {

        // 获取plist文件的路径
        NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];

        // 通过路径,获取到plist文件中的数组
        NSArray *rootArr = [NSArray arrayWithContentsOfFile:path];

        // 定义一个可变数组,向这个数组中添加模型
        NSMutableArray *tmpM = [NSMutableArray arrayWithCapacity:rootArr.count];

        // 遍历数组,取出数组中的字典
        [rootArr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            // 拿到字典之后,将字典转换成对应的模型
            AppInfo *appInfo = [AppInfo appInfoWithDict:obj];

            // 将模型添加到数据源数组中
            [tmpM addObject:appInfo];
        }];

        // 将可变数组,变成不可变 : 将线程不安全的类,变成了线程安全的类.同时,不可变的数组,外界不能修改的.
        _dataSourceArr = tmpM.copy;
    }

    return _dataSourceArr;
}

重构获取模型数组

模型类声明获取模型数组的方法

/// 获取模型数据
+ (NSArray *)appInfos;

获取模型数组的方法的实现

+ (NSArray *)appInfos
{
    // 获取plist文件的路径
    NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];

    // 通过路径,获取到plist文件中的数组
    NSArray *rootArr = [NSArray arrayWithContentsOfFile:path];

    // 定义一个可变数组,向这个数组中添加模型
    NSMutableArray *modelArrM = [NSMutableArray arrayWithCapacity:rootArr.count];

    // 遍历数组,取出数组中的字典
    [rootArr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        // 拿到字典之后,将字典转换成对应的模型
        AppInfo *appInfo = [AppInfo appInfoWithDict:obj];

        // 将模型添加到数据源数组中
        [modelArrM addObject:appInfo];
    }];

    // 将可变数组,变成不可变 : 将线程不安全的类,变成了线程安全的类.同时,不可变的数组,外界不能修改的.
    return modelArrM.copy;
}

控制器中的懒加载

- (NSArray *)dataSourceArr
{
    if (_dataSourceArr==nil) {

        // 厨子,做饭去
        _dataSourceArr = [AppInfo appInfos];
    }

    return _dataSourceArr;
}

SB中加载原形cell

关联storyboard

数据源方法

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.dataSourceArr.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 定义可重用的标示符
    static NSString *ID = @"AppCell";

    // 创建cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (cell==nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
    }

    // 返回cell
    return cell;
}

SB中加载原形cell的分析和优化

优化一 : 定义静态可重用标示符可省略.

// 这个代码课注释掉
static NSString *ID = @"AppCell";

优化二 : SB中创建列表加载原形cell - 方案1

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell==nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}

SB中创建列表加载原形cell - 方案2 更优写法

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath];
if (cell==nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}

结论

开发建议

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath];

同步下载图片

数据源方法

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 创建cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath];

    // 获取对应cell的模型
    AppInfo *app = self.dataSourceArr[indexPath.row];

    // 给cell赋值
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;

    // 模拟网络延迟
    [NSThread sleepForTimeInterval:0.5];

    // 同步下载图片
    NSURL *url = [NSURL URLWithString:app.icon];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];

    // 给imageView赋值
    cell.imageView.image = image;

    // 返回cell
    return cell;
}

存在的问题

原因

解决办法

异步下载图片

/// 全局并发队列
@property (nonatomic,strong) NSOperationQueue *queue;
- (NSOperationQueue *)queue
{
    if (_queue==nil) {
        _queue = [[NSOperationQueue alloc] init];
    }
    return _queue;
}

数据源方法 - 实现异步下载图片

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 创建cell
    // UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath];

    AppCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath];

    // 获取对应cell的模型
    AppInfo *app = self.dataSourceArr[indexPath.row];

    // 给cell赋值
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;

    // 创建异步下载操作
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        // 模拟网络延迟
        [NSThread sleepForTimeInterval:0.5];

        // 同步下载图片
        NSURL *url = [NSURL URLWithString:app.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];

        // 回到主线程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // 给imageView赋值
            cell.imageView.image = image;
        }];
    }];
    // 将操作添加到队列
    [self.queue addOperation:op];

    // 返回cell
    return cell;
}

存在的问题

原因

@implementation AppCell

// 万不得已,不要重写这个方法,更不要在这个方法里面做耗时的操作.因为调用的频率是非常高的
- (void)layoutSubviews
{
    NSLog(@"%s",__FUNCTION__);

    [super layoutSubviews];
}

@end

解决办法

设置占位图

数据源方法

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 创建cell
    AppCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath];

    // 获取对应cell的模型
    AppInfo *app = self.dataSourceArr[indexPath.row];

    // 给cell赋值
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;

    // 设置占位图 : 在cell返回之前设置
    cell.imageView.image = [UIImage imageNamed:@"user_default"];

    // 创建异步下载操作
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        // 模拟网络延迟
        [NSThread sleepForTimeInterval:0.5];

        // 同步下载图片
        NSURL *url = [NSURL URLWithString:app.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];

        // 回到主线程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // 给imageView赋值
            cell.imageView.image = image;
        }];
    }];
    // 将操作添加到队列
    [self.queue addOperation:op];

    // 返回cell
    return cell;
}

存在的问题

原因

解决办法

字典实现图片内存缓存

准备图片缓存池

/// 图片缓存池
@property (nonatomic,strong) NSMutableDictionary *imageCache;
- (NSMutableDictionary *)imageCache
{
    if (_imageCache==nil) {
        _imageCache = [[NSMutableDictionary alloc] init];
    }
    return _imageCache;
}

数据源方法

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 创建cell
    AppCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath];

    // 获取对应cell的模型
    AppInfo *app = self.dataSourceArr[indexPath.row];

    // 给cell赋值
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;

    // 设置占位图 : 在cell返回之前设置
    cell.imageView.image = [UIImage imageNamed:@"user_default"];

    // 在建立下载操作之前,判断图片缓存池内部是否有图片对象
    if ([self.imageCache objectForKey:app.icon]!=nil) {
        NSLog(@"从内存加载...%@",app.name);
        cell.imageView.image = [self.imageCache objectForKey:app.icon];
        return cell;
    }

    // 创建异步下载操作
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        // 模拟网络延迟
        [NSThread sleepForTimeInterval:0.5];

        NSLog(@"从网络加载...%@",app.name);

        // 同步下载图片
        NSURL *url = [NSURL URLWithString:app.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];

        // 下载完成之后,回到主线程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{

            // 给imageView赋值
            cell.imageView.image = image;

            // 保存图片到图片缓存池
            [self.imageCache setObject:image forKey:app.icon];
        }];
    }];
    // 将操作添加到队列
    [self.queue addOperation:op];

    // 返回cell
    return cell;
}

目前为止我们的需求实现了吗?

解决图片错行的问题

在下载操作中模拟网络延迟

// 模拟网络延迟
if (indexPath.row>9) {
    [NSThread sleepForTimeInterval:5.0];
}

问题

分析问题

解决问题

// 刷新对应的行
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];

数据源方法

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 创建cell
    AppCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath];

    // 获取对应cell的模型
    AppInfo *app = self.dataSourceArr[indexPath.row];

    // 给cell赋值
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;

    // 设置占位图 : 在cell返回之前设置
    cell.imageView.image = [UIImage imageNamed:@"user_default"];

    // 在建立下载操作之前,判断图片缓存池内部是否有图片对象
    if ([self.imageCache objectForKey:app.icon]!=nil) {
        NSLog(@"从内存加载...%@",app.name);
        cell.imageView.image = [self.imageCache objectForKey:app.icon];
        return cell;
    }

    // 创建异步下载操作
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        // 模拟网络延迟
        if (indexPath.row>9) {
            [NSThread sleepForTimeInterval:5.0];
        }

        NSLog(@"从网络加载...%@",app.name);

        // 同步下载图片
        NSURL *url = [NSURL URLWithString:app.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];

        // 回到主线程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{

            // 给imageView赋值
//            cell.imageView.image = image;

            // 保存图片到图片缓存池
            [self.imageCache setObject:image forKey:app.icon];

            // 刷新对应的行
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }];
    }];
    // 将操作添加到队列
    [self.queue addOperation:op];

    // 返回cell
    return cell;
}

注意

目前为止我们的需求实现了吗?

解决图片重复下载的问题

在下载操作中模拟网络延迟

// 模拟网络延迟
if (indexPath.row>9) {
    [NSThread sleepForTimeInterval:20.0];
}

监听cell的点击事件,获取队列的操作计数

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"队列中的操作个数 %zd",self.queue.operationCount);
}

问题

分析问题

解决问题

准备下载操作缓冲池

/// 下载操作缓冲池
@property (nonatomic,strong) NSMutableDictionary *operationCache;
- (NSMutableDictionary *)operationCache
{
    if (_operationCache==nil) {
        _operationCache = [[NSMutableDictionary alloc] init];
    }
    return _operationCache;
}

数据源方法

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 创建cell
    AppCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath];

    // 获取对应cell的模型
    AppInfo *app = self.dataSourceArr[indexPath.row];

    // 给cell赋值
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;

    // 设置占位图 : 在cell返回之前设置
    cell.imageView.image = [UIImage imageNamed:@"user_default"];

    // 在建立下载操作之前,判断图片缓存池内部是否有图片对象
    if ([self.imageCache objectForKey:app.icon]!=nil) {
        NSLog(@"从内存加载...%@",app.name);
        cell.imageView.image = [self.imageCache objectForKey:app.icon];
        return cell;
    }

    // 在建立下载操作之前,对应的图片的判断下载操作有没有
    if ([self.operationCache objectForKey:app.icon]!=nil) {
        NSLog(@"%@ 正在下载中...",app.name);
        return cell;
    }

    // 创建异步下载操作
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        // 模拟网络延迟
        if (indexPath.row>9) {
            [NSThread sleepForTimeInterval:20.0];
        }

        NSLog(@"从网络加载...%@",app.name);

        // 同步下载图片
        NSURL *url = [NSURL URLWithString:app.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];

        // 图片下载完成,回到主线程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{

            // 保存图片到图片缓存池
            [self.imageCache setObject:image forKey:app.icon];

            // 下载完成之后,清理对应的下载操作
            [self.operationCache removeObjectForKey:app.icon];

            // 刷新对应的行
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }];
    }];

    // 将下载操作添加到缓冲池
    [self.operationCache setObject:op forKey:app.icon];
    // 将操作添加到队列 : 操作执行结束之后,会自动从队列中移除,一旦移除,就解除了循环引用
    [self.queue addOperation:op];

    // 返回cell
    return cell;
}

到目前为止我们的需求实现了吗?

处理内存警告

内存警告

处理内存警告的实现

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.

    // 清理图片缓冲池
    [self.imageCache removeAllObjects];
    // 清理下载操作缓冲池
    [self.operationCache removeAllObjects];
    //取消所有的下载操作 : 正在下载的无法取消的.我们需要自定义下载操作才可以取消正在下载的操作
    [self.queue cancelAllOperations];
}

到目前为止我们的需求实现了吗?

解除循环引用

循环引用的分析

循环引用分析.png

代码验证循环引用

- (void)dealloc
{
    NSLog(@"%s",__FUNCTION__);
}

为什么代码验证没有循环引用?

  1. 队列queue对下载操作的强引用关系.当下载操作完成之后,下载操作会自动从队列中移除,强引用关系也就解除了.
  2. 下载操作缓存池对下载操作的强引用关系.当图片下载完成之后,下载操作已经手动的从下载操作缓存池中移除了.
// 图片下载完成,回到主线程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{

    // 保存图片到图片缓存池
    [self.imageCache setObject:image forKey:app.icon];

    // 下载完成之后,清理对应的下载操作
    [self.operationCache removeObjectForKey:app.icon];

    // 刷新对应的行
    [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}];

隐含的问题

解除循环引用

// 可以及时的解除循环引用
__weak typeof(self) weakSelf = self;

// 创建异步下载操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    // 模拟网络延迟
    if (indexPath.row>9) {
        [NSThread sleepForTimeInterval:5.0];
    }

    NSLog(@"从网络加载...%@",app.name);

    // 同步下载图片
    NSURL *url = [NSURL URLWithString:app.icon];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];

    // 回到主线程更新UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

        // 保存图片到图片缓存池
        [weakSelf.imageCache setObject:image forKey:app.icon];

        // 下载完成之后,清理对应的下载操作 : 也可以解除循环引用
        [weakSelf.operationCache removeObjectForKey:app.icon];

        // 刷新对应的行
        [weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
    }];
}];

到目前为止我们的需求实现了吗?

断网测试

问题

解决问题

__weak typeof(self) weakSelf = self;

// 创建异步下载操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    // 模拟网络延迟
    if (indexPath.row>9) {
        [NSThread sleepForTimeInterval:5.0];
    }

    NSLog(@"从网络加载...%@",app.name);

    // 同步下载图片
    NSURL *url = [NSURL URLWithString:app.icon];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];

    // 回到主线程更新UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

        // 断网测试
        if (image!=nil) {
            // 保存图片到图片缓存池
            [weakSelf.imageCache setObject:image forKey:app.icon];

            // 下载完成之后,清理对应的下载操作 : 也可以解除循环引用
            [weakSelf.operationCache removeObjectForKey:app.icon];

            // 刷新对应的行
            [weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }
    }];
}];

到目前为止我们的需求实现了吗?

自定义Cell

实现自动布局

自动布局.png

连线使自定义cell子控件建立关联

@interface AppCell : UITableViewCell

/// App图标
@property (weak, nonatomic) IBOutlet UIImageView *iconImageView;
// App名字
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
// App下载量
@property (weak, nonatomic) IBOutlet UILabel *downloadLabel;

/// 模型
@property (nonatomic,strong) AppInfo *appInfo;

@end

给自定义cell上的子控件赋值

@interface AppCell ()

@end

@implementation AppCell

- (void)setAppInfo:(AppInfo *)appInfo
{
    // 给cell赋值
    self.nameLabel.text = appInfo.name;
    self.downloadLabel.text = appInfo.download;
}

@end

数据源方法

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 创建cell
    AppCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath];

    // 获取对应cell的模型
    AppInfo *app = self.dataSourceArr[indexPath.row];

    // 给cell传递数据
    cell.appInfo = app;

    // 设置占位图 : 在cell返回之前设置
    cell.iconImageView.image = [UIImage imageNamed:@"user_default"];

    // 在建立下载操作之前,判断内存缓存内部是否有图片对象
    if ([self.imageCache objectForKey:app.icon]!=nil) {
        NSLog(@"从内存加载...%@",app.name);
        cell.iconImageView.image = [self.imageCache objectForKey:app.icon];
        return cell;
    }

    // 在建立下载操作之前,对应的图片的判断下载操作有没有
    if ([self.operationCache objectForKey:app.icon]!=nil) {
        NSLog(@"%@ 正在下载中...",app.name);
        return cell;
    }

    // 可以及时的解除循环引用
    __weak typeof(self) weakSelf = self;
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        // 模拟网络延迟
        if (indexPath.row>9) {
            [NSThread sleepForTimeInterval:0.0];
        }

        NSLog(@"从网络加载...%@",app.name);

        // 同步下载图片
        NSURL *url = [NSURL URLWithString:app.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];

        // 回到主线程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{

            // 断网测试
            if (image!=nil) {
                // 保存图片到图片缓存池
                [weakSelf.imageCache setObject:image forKey:app.icon];

                // 下载完成之后,清理对应的下载操作 : 也可以解除循环引用
                [weakSelf.operationCache removeObjectForKey:app.icon];

                // 刷新对应的行
                [weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            }
        }];
    }];

    // 将下载操作添加到缓冲池
    [self.operationCache setObject:op forKey:app.icon];
    // 将操作添加到队列 : 操作执行结束之后,会自动从队列中移除,一旦移除,就解除了循环引用
    [self.queue addOperation:op];

    // 返回cell
    return cell;
}

到目前为止我们的需求实现了吗?

沙盒演练

沙盒目录介绍

沙盒演练

- (void)appendDocumentsPath
{
    // 图片地址
    NSString *icon = @"http://p16.qhimg.com/dr/48_48_/t0125e8d438ae9d2fbb.png";

    // 获取Documents文件目录
    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    // 获取图片的名字
    NSString *fileName = [icon lastPathComponent];
    // Documents文件目录拼接图片的名字 == 图片保存到沙盒的路径
    NSString *filePath = [documentsPath stringByAppendingPathComponent:fileName];
}
- (void)appendCachePath
{
    // 图片地址
    NSString *icon = @"http://p16.qhimg.com/dr/48_48_/t0125e8d438ae9d2fbb.png";

    // 获取Cache文件目录
    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    // 获取图片的名字
    NSString *fileName = [icon lastPathComponent];
    // cache文件目录拼接图片的名字 == 图片保存到沙盒的路径
    NSString *filePath = [cachePath stringByAppendingPathComponent:fileName];
}
- (void)appendTmpPath
{
    // 图片地址
    NSString *icon = @"http://p16.qhimg.com/dr/48_48_/t0125e8d438ae9d2fbb.png";

    // 获取Tmp文件目录
    NSString *tmpPath = NSTemporaryDirectory();
    // 获取图片的名字
    NSString *fileName = [icon lastPathComponent];
    // Tmp文件目录拼接图片的名字 == 图片保存到沙盒的路径
    NSString *filePath = [tmpPath stringByAppendingPathComponent:fileName];
}

沙盒实现磁盘缓存

创建NSString+path分类

@interface NSString (path)

/// 文件保存到Documents目录
- (NSString *)appendDocumentsPath;
/// 文件保存到Cache目录
- (NSString *)appendCachePath;
/// 文件保存到Tmp目录
- (NSString *)appendTmpPath;

@end
@implementation NSString (path)

/// 文件保存到Documents目录
- (NSString *)appendDocumentsPath
{
    // 获取Documents文件目录
    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    // 获取图片的名字
    NSString *fileName = [self lastPathComponent];
    // Documents文件目录拼接图片的名字 == 图片保存到沙盒的路径
    NSString *filePath = [documentsPath stringByAppendingPathComponent:fileName];

    return filePath;
}

/// 文件保存到Cache目录
- (NSString *)appendCachePath
{
    // 获取Cache文件目录
    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    // 获取图片的名字
    NSString *fileName = [self lastPathComponent];
    // cache文件目录拼接图片的名字 == 图片保存到沙盒的路径
    NSString *filePath = [cachePath stringByAppendingPathComponent:fileName];

    return filePath;
}

/// 文件保存到Tmp目录
- (NSString *)appendTmpPath
{
    // 获取Tmp文件目录
    NSString *tmpPath = NSTemporaryDirectory();
    // 获取图片的名字
    NSString *fileName = [self lastPathComponent];
    // Tmp文件目录拼接图片的名字 == 图片保存到沙盒的路径
    NSString *filePath = [tmpPath stringByAppendingPathComponent:fileName];

    return filePath;
}

@end

分类使用

导入头文件 NSString+path

// 可以及时的解除循环引用
__weak typeof(self) weakSelf = self;
// 创建异步下载操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    // 模拟网络延迟
    if (indexPath.row>9) {
        [NSThread sleepForTimeInterval:0];
    }

    NSLog(@"从网络加载...%@",app.name);

    // 同步下载图片
    NSURL *url = [NSURL URLWithString:app.icon];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];

    // 图片下载完成之后,做沙盒缓存
    if (image!=nil) {
        [data writeToFile:[app.icon appendCachePath] atomically:YES];
    }

    // 回到主线程更新UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

        // 断网测试
        if (image!=nil) {
            // 保存图片到图片缓存池
            [weakSelf.imageCache setObject:image forKey:app.icon];

            // 下载完成之后,清理对应的下载操作 : 也可以解除循环引用
            [weakSelf.operationCache removeObjectForKey:app.icon];

            // 刷新对应的行
            [weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }
    }];
}];

// 将下载操作添加到缓冲池
[self.operationCache setObject:op forKey:app.icon];
// 将操作添加到队列 : 操作执行结束之后,会自动从队列中移除,一旦移除,就解除了循环引用
[self.queue addOperation:op];
// 在建立下载操作之前,判断内存缓存内部是否有图片对象
if ([self.imageCache objectForKey:app.icon]!=nil) {
    NSLog(@"从内存加载...%@",app.name);
    cell.iconImageView.image = [self.imageCache objectForKey:app.icon];
    return cell;
}

// 判断沙盒有没有缓存图片
NSData *data = [NSData dataWithContentsOfFile:[app.icon appendCachePath]];
UIImage *image = [UIImage imageWithData:data];
if (image!=nil) {
    NSLog(@"从沙盒加载...%@",app.name);
    // 在内存中保存一份
    [self.imageCache setObject:image forKey:app.icon];

    cell.iconImageView.image = image;
    return cell;
}

// 在建立下载操作之前,对应的图片的判断下载操作有没有
if ([self.operationCache objectForKey:app.icon]!=nil) {
    NSLog(@"%@ 正在下载中...",app.name);
    return cell;
}

到目前为止我们的需求实现了吗?

代码重构

重构目的

重构的步骤

到目前为止我们的需求实现了吗?

上一篇 下一篇

猜你喜欢

热点阅读