多线程04 下载网络Json数据 理解异步下载图片处理沙盒内存线程

使用第三方框架#import "AFNetworking.h"下载网络Json数据

- (void)loadJSONData
{
    // 1.创建网络请求管理者
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    // 2.使用网络请求管理者,发送网络请求获取json数据
    [manager GET:@"https://raw.githubusercontent.com/zhangxiaochuZXC/SZiOS07_FerverFile/master/apps.json" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        
        // responseObject : 发送请求需要获取的数据
        NSLog(@"%@-%@-%@",[responseObject class],responseObject,[NSThread currentThread]);
        
        // 下一步 : 实现字典转模型
        
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"%@",error);
    }];
}

使用第三方框架#import "YYModel.h"转换模型

第一个参数需要先创建模型 传入模型的类型
第二个数据需要传已经解析好的Json数据
NSArray *appList = [NSArray yy_modelArrayWithClass:[APPModel class] json:dictArr];

使用第三方框架"UIImageView+WebCache.h"(SDWebImage)下载网络图片

第一个参数传的是网络图片的地址
第二个参数传的是占位图 直接传入以图片对象即可

[imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] 
placeholderImage:[UIImage imageNamed:@"user_default"]];

练习模拟企业异步下载图片

需求 : 列表异步加载网络图片

"SDWebImage实现列表异步加载网络图片" : 一定要掌握

1.需求分析
1.1 UI / 界面分析 : 了解界面怎么布局 / 怎么实现 (使用哪些UI控件)
1.2 数据结构分析 : 了解数据结构是为了了解如何实现字典转模型(字典数组转模型数组 / 字典转模型)

2.获取要zha展示的json数据
2.0 https://raw.githubusercontent.com/zhangxiaochuZXC/SZiOS07_FerverFile/master/apps.json
2.1 拿到json数据对应的地址URL
2.2 使用AFN发送请求,获取json数据
2.3 AFN默认是在子线程异步获取网络数据,然后自动回到主线程调用success代码块
2.4 AFN默认帮我们实现了json的反序列化

3.YYModel实现字典数组转模型数组
3.1 拿到responseObject之后,需要使用YYModel实现字典数组转模型数组
self.appList = [NSArray yy_modelArrayWithClass:[APPModel class] json:dictArr];

3.2 字典数组转模型数组之后,得到数据源数组

4.刷新UI / 自定义cell
4.1 得到数据源数组之后,一定要调用

[self.tableView reloadData];

5.SDWebImage实现图片的下载和展示
5.1 SD这个框架是自动在子线程下载图片,回到主线程给图片空间赋值
5.2 在使用SD框架时,一定要设置展位图;不然,控件不会加载上去

[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"user_default"]];

----------------------------------华丽的分割线-----------------------------------------

1.使用NSBlockOperation实现图片异步下载和在主线程刷洗UI
1.1 如果没有占位图,在图片下载结束之前,展示图片的位置,是一块空白,影响美观和用户体验
1.2 提示 : 将来做图片异步下载时,一定要记得先设置占位图

2.设置占位图
2.1 在建立异步的下载操作之前,先设置占位图;占位图放在项目的图片库

3.实现内存缓存
3.1 如果不实现内存缓存,cell上的图片每次展示之前都要从网络中加载
3.1 使用可变字典制作图片内存缓存池
3.2 图片下载完成之后,把非空的图片对象,添加到图片缓存池
3.4 在建立异步下载操作之前,判断要下载的图片在图片缓存池中有木有;如果有,就直接取出来展示;反之,就去下载;

4.解决图片错行的问题
4.1 当有网络延迟时,在cell上的图片还没有加载出来之前,来回的滚动cell,由于cell的复用机制,图片会错行展示
4.2 图片错行展示的本质 : 就是后面下载完成的图片,把前面下载完成的图片覆盖了
4.3 解决图片错行的办法 : 哪行cell上的图片下载完,就刷新哪行;拒绝使用重用的cell

5.解决重复建立下载操作的问题
5.1 当有网络延迟时,在cell上的图片还没有加载出来之前,来回的滚动cell,就会多次为同一张图片建立多个下载操作
5.2 解决办法 : 使用可变字典制作操作缓存池.在建立下载操作之前,判断要建立的下载操作,在缓存池里面有没有;如果有,就不在建立下载操作;反之,就建立下载操作
5.3 图片下载完成之后,不管下载的内容是否是空,都需要移除对应的下载操作

6.处理内存警告
6.1 目的 : 保证内存使用时的平衡

7.实现沙盒缓存
7.1 沙盒缓存只在程序重启时使用的
7.2 操作内存比操作沙盒效率高
7.3 在判断内存缓存之后,建立下载操作之前,判断沙盒缓存
7.4 程序重启后,如果沙盒里面有图片,就把沙盒数据再在内存中存一份

#import "ViewController.h"
#import "AFNetworking.h"
#import "YYModel.h"
#import "APPModel.h"
#import "APPCell.h"
#import "NSString+path.h"

@interface ViewController ()

/* 数据源数组 */
@property (strong, nonatomic) NSArray *appList;
/* 队列 */
@property (strong, nonatomic) NSOperationQueue *queue;
/* 图片缓存池 */
@property (strong, nonatomic) NSMutableDictionary *imagesCache;
/* 操作缓存池 */
@property (strong, nonatomic) NSMutableDictionary *OPCache;

@end

// 相当于后台人员提供给你的开发接口 : https://raw.githubusercontent.com/zhangxiaochuZXC/SZiOS07_FerverFile/master/apps.json
// 将来就是拿着这个URL去获取数据

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 实例化队列
    self.queue = [[NSOperationQueue alloc] init];
    // 实例化图片缓存池
    self.imagesCache = [[NSMutableDictionary alloc] init];
    // 实例化操作缓存池
    self.OPCache = [[NSMutableDictionary alloc] init];
    
    [self loadJSONData];
}

#pragma mark-获取json数据的主方法
- (void)loadJSONData
{
    // 1.创建网络请求管理者
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    // 2.使用网络请求管理者,发送网络请求获取json数据;
    // GET方法默认是在子线程异步执行的,当AFN获取到网络数据之后,success回调是自动的默认在主线程执行的
    [manager GET:@"https://raw.githubusercontent.com/zhangxiaochuZXC/SZiOS07_FerverFile/master/apps.json" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        
        // responseObject就是字典数组 (AFN自动实现字典转模型)
        NSArray *dictArr = responseObject;
        // 实现字典转模型 : 字典数组转模型数组
        self.appList = [NSArray yy_modelArrayWithClass:[APPModel class] json:dictArr];
//        NSLog(@"%@",self.appList);
        
        // 拿到数据源数组之后,在主线程刷新UI,实现数据源方法
        [self.tableView reloadData];
        
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"%@",error);
    }];
}

#pragma mark-实现数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.appList.count;
}

/*
 1.创建异步操作,在子线程下载图片,在主线程刷新UI
    问题 : 当图片没有下载完,控件是白板
    解决 : 在下载之前,设置占位图
 2.设置占位图
 3.实现内存缓存策略 (因为不做缓存,图片都是从网络中加载)
    解决 : 使用字典实现内存缓存,当要下载的图片在内存中已经有了,就不需要再建立下载操作了
 4.当有网络延迟时,来回滚动cell,由于cell的服用机制,就造成了图片的错位
    解决 : 刷新对应行
 5.当有网络延迟时,在cell上的图片还没有下载完之前,来回的滚动cell,会重复的建立下载操作
    解决 : 使用字典创建操作缓存池,每次建立下载操作之前,先判断要建立的下载操作有没有,如果有,就不在建立下载操作
 6.处理内存警告 : 目的就是为了控制内存平衡,保证程序不因为内存警告而闪退
 7.实现沙盒缓存 : 沙盒缓存只在程序重新启动时有用 / 需要把沙盒数据再在内存中存一份 : 操作内存的效率高于沙盒
 */

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    APPCell *cell = [tableView dequeueReusableCellWithIdentifier:@"APPCell" forIndexPath:indexPath];
    
    // 获取cell对应的模型数据
    APPModel *app = self.appList[indexPath.row];
    // 给cell的label子控件赋值
    cell.nameLabel.text = app.name;
    cell.downloadLabel.text = app.download;
    
    // 设置占位图
    cell.iconImageView.image = [UIImage imageNamed:@"user_default"];
    
    // 在建立下载操作之前,判断要下载的图片在图片缓存池中有没有,如果有,就直接读取,并展示
    UIImage *memImage = [self.imagesCache objectForKey:app.icon];
    if (memImage != nil) {
        NSLog(@"从内存中加载... %@",app.name);
        cell.iconImageView.image = memImage;
        return cell;
    }
    
    // 在建立下载操作之前,和判断内存缓存之后,判断是否有沙盒缓存;(提示 : 操作内存的效率高于沙盒)/
    // 沙盒缓存只在程序重新启动时有用
    UIImage *cacheImage = [UIImage imageWithContentsOfFile:[app.icon appendCaches]];
    if (cacheImage != nil) {
        NSLog(@"从沙盒中加载... %@",app.name);
        // 需要再在内存中存一份 : 操作内存的效率高于沙盒
        [self.imagesCache setObject:cacheImage forKey:app.icon];
        cell.iconImageView.image = cacheImage;
        return cell;
    }
    
    // 在建立下载操作之前,判断要建立的下载操作在操作缓存池里面有木有,如果有,就不在建立下载操作
    if ([self.OPCache objectForKey:app.icon] != nil) {
        NSLog(@"正在下载中... %@",app.name);
        return cell;
    }
    
#pragma mark-给cell的iconImageView赋值,先在子线程异步下载,再在主线程赋值
    
    // 创建异步操作
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"从网络中加载... %@",app.name);
        
        // 模拟网络延迟 : 实际开发中,每行cell的图片的大小有可能差别比较大,造成有些cell图片展示的比较快,有些比较慢,模拟[屏幕之外的cell延迟加载
//        if (indexPath.row > 9) {
//            [NSThread sleepForTimeInterval:60.0];
//        }
        
        // URL
        NSURL *URL = [NSURL URLWithString:app.icon];
        //data : 耗时的操作,最多执行60秒,60秒之后,如果图片没有下载下来,会返回nil
        NSData *data = [NSData dataWithContentsOfURL:URL];
        // image
        UIImage *image = [UIImage imageWithData:data];
        
        // 实现沙盒缓存 : 当image不为空时,才缓存到沙盒
        if (image != nil) {
            [data writeToFile:[app.icon appendCaches] atomically:YES];
        }
        
        // 当image下载完成之后,通知主线程刷新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        
            // 图片下载完成之后要做的事情,
            if (image != nil) {
                // 把图片缓存到图片缓存池
                [self.imagesCache setObject:image forKey:app.icon];
                
                // 图片下载完成之后,刷新对应的行,会再次调用cellForRowAtIndexPath
                [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            }
            
            // 当图片下载完成之后,把图片对应的下载操作,从操作缓存池移除,保证了内存平衡和保证了操作魂村吃里面只有没有执行完的操作
            [self.OPCache removeObjectForKey:app.icon];
        }];
    }];
    // 把下载操作添加到操作缓存池
    [self.OPCache setObject:op forKey:app.icon];
    // 把异步操作添加到队列
    [self.queue addOperation:op];
    
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"%tu",self.queue.operationCount);
}

// 当系统觉得内存不够用时,会发送内存警告的通知
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    
    // 一定要清空图片缓存池
    [self.imagesCache removeAllObjects];
    // 可以清空操作缓存池
    [self.OPCache removeAllObjects];
    // 可以清空队列
    [self.queue cancelAllOperations];
}


@end

你可能感兴趣的:(多线程04 下载网络Json数据 理解异步下载图片处理沙盒内存线程)