使用第三方框架#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