iOS多线程 一一 多图下载程序
来源:互联网 发布:任加踢法 知乎 编辑:程序博客网 时间:2024/06/04 09:54
效果图如下:
打印效果:
上图打印效果,展现了滚动tableView重复从网络中下载数据的现象,在后面会对上面打印做介绍.
涉及到的知识点:
01 字典转模型 02 存储数据到沙盒,从沙盒中加载数据 03 占位图片的设置(cell的刷新问题) 04 如何进行内存缓存(使用NSDictionary) 05 在程序开发过程中的一些容错处理 06 如何刷新tableView的指定行(解决数据错乱问题) 07 NSOperation以及线程间通信相关知识
看效果图,感觉很简单,创建一个UITableView,在cell上面设置数据. 以前在都是一些现成的数据,这次试用的数据(图片)是通过URL从网络中下载来的,因此会出现很多问题!
比如:
1. UI很不流畅--------> 开子线程下载图片
2. 图片重复从网络中下载 --------> 把下载过的图片保存起来
3. 图片不会自动刷新
4. 当网络延迟时,图片又会重复下载
5. 数据错乱现象.
首先不考虑上面出现的问题,先把上面的效果图做好.然后再根据上面问题逐一解决.
storyboard
ZYTableViewController文件
这个tableViewController和storyboard中的控制器是绑定好的.
//// ZYTableViewController.h// 00-掌握-多图下载综合案例-数据展示//// Created by 朝阳 on 2017/11/22.// Copyright © 2017年 sunny. All rights reserved.//#import <UIKit/UIKit.h>@interface ZYTableViewController : UITableViewController@end#import "ZYTableViewController.h"#import "ZYApps.h"@interface ZYTableViewController ()@property (nonatomic, strong) NSArray *apps;@end@implementation ZYTableViewController#pragma -mark lazy loading- (NSArray *)apps{ if (!_apps) { // 加载plist文件 NSArray *arrayM = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]]; // 创建一个临时可变数组 NSMutableArray *tempArray = [NSMutableArray array]; for (NSDictionary *dict in arrayM) { [tempArray addObject:[ZYApps appWithDict:dict]]; } _apps = tempArray; } return _apps;}- (void)viewDidLoad { [super viewDidLoad]; }#pragma mark - Table view data source- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1;}- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.apps.count;}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *ID = @"app"; // 创建cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID forIndexPath:indexPath]; // 设置数据给cell ZYApps *app = self.apps[indexPath.row]; cell.textLabel.text = app.name; cell.detailTextLabel.text = app.download; NSURL *url = [NSURL URLWithString:app.icon]; NSData *iconData = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:iconData]; cell.imageView.image = image; // NSLog(@"%ld----",indexPath.row);/* 存在两个严重问题: 1. UI很不流畅 ----> 开子线程下载图片 2. 图片重复下载 ----> 先把之前已经下载的图片保存起来 */ // 返回cell return cell;}@end
模型数据ZYApps文件
//// ZYApps.h// 00-掌握-多图下载综合案例-数据展示//// Created by 朝阳 on 2017/11/22.// Copyright © 2017年 sunny. All rights reserved.//#import <Foundation/Foundation.h>@interface ZYApps : NSObject/** app名称 */@property(nonatomic, strong) NSString * name;/** app下载量 */@property(nonatomic, strong) NSString * download;/** app图标 */@property(nonatomic, strong) NSString * icon;+ (instancetype)appWithDict:(NSDictionary *)dict;@end@implementation ZYApps+ (instancetype)appWithDict:(NSDictionary *)dict{ ZYApps *apps = [[ZYApps alloc] init]; // 利用KVC [apps setValuesForKeysWithDictionary:dict]; return apps;}@end
1. 图片被重复下载
2. UI很不流畅
问题1. 图片被重复下载
因为当滚动tableView的时候,会重复下载网络中的图片.----解决---> 先把下载好的图片保存起来
具体解决:
当应用程序第一次下载下来的时候,tableView中的图片,需要从网络中下载下来.然后把图片保存到内存缓存一份,把图片也写入到沙盒中一份.
当来回滚动tableView的时候,下载过的图片已经在内存中缓存过了,因此获取内存中的图片就可以了.由此防止了重复下载图片的现象.把图片的二进制写入到沙盒中,原因是
因为当应用程序重新启动的时候,在应用程序内存中缓存的图片都清空了,因此还需要重新从网络上下载图片,保存到沙盒中就是为了当重新启动应用程序的时候,数据可以从沙盒中读取,防止重复下载.
此时 tableView:cellForRowAtIndexPath:方法中代码.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *ID = @"app"; // 创建cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID forIndexPath:indexPath]; // 设置数据给cell ZYApps *app = self.apps[indexPath.row]; cell.textLabel.text = app.name; cell.detailTextLabel.text = app.download; // 设置图标 // 查看内存缓存中该图片是否存在,若存在直接用,否则去磁盘缓存中查看是否有缓存\ 如果有磁盘缓存,就保存一份到内存.设置图片,否则下载 // 从内存缓存中读取 UIImage *image = [self.images objectForKey:app.icon]; // 是否内存中存在已下载的图片 if (image) { cell.imageView.image = image; NSLog(@"使用内存缓存中的图片---%ld",indexPath.row); }else{ // 保存图片到沙盒缓存 /* arg1: 沙盒的哪个目录 arg2: 去主目录下去搜索,默认就是NSUserDomainMask arg3: 是否展开路径 */ NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; // 获得图片名称,不能包含/ NSString *fileName = [app.icon lastPathComponent]; // 拼接图片的全路径 NSString *fullPath = [caches stringByAppendingPathComponent:fileName]; // 检查磁盘缓存 NSData *imageData = [NSData dataWithContentsOfFile:fullPath]; if (imageData) { UIImage *image = [UIImage imageWithData:imageData]; // 设置图标 cell.imageView.image = image; NSLog(@"%ld--使用了磁盘缓存的图片--",indexPath.row); // 把图片保存到内存中一份 [self.images setObject:image forKey:app.icon];// NSLog(@"%@",fullPath); }else{ NSURL *url = [NSURL URLWithString:app.icon]; NSData *iconData = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:iconData]; cell.imageView.image = image; // 把图片保存到内存缓存 [self.images setObject:image forKey:app.icon]; // 写数据到沙盒 [iconData writeToFile:fullPath atomically:YES];// NSLog(@"%@",fullPath); NSLog(@"%ld--下载--",indexPath.row); } } /* 存在两个严重问题: 1. UI很不流畅 ----> 开子线程下载图片 2. 图片重复下载 ----> 先把之前已经下载的图片保存起来 */ // 返回cell return cell;}
这样就解决了重复下载图片.
解决上面的5个问题 和 ZYTableViewController文件
问题:
1. UI很不流畅 --->开子线程下载图片
2. 图片重复下载 --->先把之前已经下载的图片保存起来(字典)
内存缓存 ---> 磁盘缓存
3. 图片不会自动刷新:
原因:因为cell是subTitle类型的,subTitle类型中的image默认frame是(0,0,0,0)的,当显示cell的时候,image的frame还是(0,0,0,0),此时有图片已经下载完了.因为是开子线程下载图片的,程序是异步的,因此先return cell,此时的cell的Image的frame为0,图片设置上去也是不显示的.
解决:手动刷新每一行cell. reloadRowsAtIndexPaths:withRowAnimation:,这个方法会调用cellForRow方法,因此会重新创建cell,cell的Image此时已经在内存缓存了.
4.(当网络延迟时)图片重复下载:
因为当下载一个cell的图片时候需要2s,当这个cell下载到1s的时候,用户滚动速度较快,此时整个cell被存放到缓存池中了(此时cell的图片还没有下载完),当下一个cell显示的时候,会从缓存池中取,此时缓存池中没有下载好图片的cell.因此会出现重复下载现象
5. 数据错乱
原因: cell的复用问题造成的,当从缓存池中复用cell的同时,把复用的cell的图片也复用过来了.因此出现数据紊乱现象
解决:当cell需要下载新的图片之前,清空cell原来的图片(设置占位图片)
#import "ZYTableViewController.h"#import "ZYApps.h"@interface ZYTableViewController ()/** 模型数组 */@property (nonatomic, strong) NSArray *apps;/** 存放下载过的图片 */@property (nonatomic,strong) NSMutableDictionary *images;/** 队列 */@property (nonatomic,strong) NSOperationQueue *queue;/** 操作缓存 */@property (nonatomic,strong) NSMutableDictionary *operations;@end@implementation ZYTableViewController#pragma -mark lazy loading- (NSArray *)apps{ if (!_apps) { // 加载plist文件 NSArray *arrayM = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]]; // 创建一个临时可变数组 NSMutableArray *tempArray = [NSMutableArray array]; for (NSDictionary *dict in arrayM) { [tempArray addObject:[ZYApps appWithDict:dict]]; } _apps = tempArray; } return _apps;}- (NSMutableDictionary *)images{ if (!_images) { _images = [NSMutableDictionary dictionary]; } return _images;}- (NSOperationQueue *)queue{ if(!_queue){ _queue = [[NSOperationQueue alloc] init]; // 设置最大并发数:并行执行的任务数 _queue.maxConcurrentOperationCount = 5; } return _queue;}- (NSMutableDictionary *)operations{ if (!_operations) { _operations = [NSMutableDictionary dictionary]; } return _operations;}- (void)viewDidLoad { [super viewDidLoad]; }- (void)didReceiveMemoryWarning{ // 当发生内存警告的时候 [self.images removeAllObjects]; // 取消队列中所有的操作 [self.queue cancelAllOperations];}#pragma mark - Table view data source- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.apps.count;}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *ID = @"app"; // 创建cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID forIndexPath:indexPath]; // 设置数据给cell ZYApps *app = self.apps[indexPath.row]; cell.textLabel.text = app.name; cell.detailTextLabel.text = app.download; // 设置图标 // 查看内存缓存中该图片是否存在,若存在直接用,否则去磁盘缓存中查看是否有缓存\ 如果有磁盘缓存,就保存一份到内存.设置图片,否则下载 // 从内存缓存中读取 UIImage *image = [self.images objectForKey:app.icon]; // 是否内存中存在已下载的图片 if (image) { cell.imageView.image = image; NSLog(@"使用内存缓存中的图片---%ld",indexPath.row); }else{ // 保存图片到沙盒缓存 /* arg1: 沙盒的哪个目录 arg2: 去主目录下去搜索,默认就是NSUserDomainMask arg3: 是否展开路径 */ NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; // 获得图片名称,不能包含/ NSString *fileName = [app.icon lastPathComponent]; // 拼接图片的全路径 NSString *fullPath = [caches stringByAppendingPathComponent:fileName]; // 检查磁盘缓存 NSData *imageData = [NSData dataWithContentsOfFile:fullPath]; // 废除// imageData = nil; if (imageData) { UIImage *image = [UIImage imageWithData:imageData]; // 设置图标 cell.imageView.image = image; NSLog(@"%ld--使用了磁盘缓存的图片--",indexPath.row); // 把图片保存到内存中一份 [self.images setObject:image forKey:app.icon]; // NSLog(@"%@",fullPath); }else{ // 创建队列(注意:在这里会创建很多个队列); // NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //## 检查该图片是否正在下载,如果是那么就什么都不做,否则再添加下载任务 NSBlockOperation *downloadImage = [self.operations objectForKey:app.icon]; if (downloadImage) { // 什么都不做 }else{ // 清空cell之前的图片// cell.imageView.image = nil; // 占位图片// cell.imageView.image = [UIImage imageNamed:@"qq"]; // 创建操作 downloadImage = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:app.icon]; NSData *iconData = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:iconData]; //NSLog(@"%ld--下载--",indexPath.row); // 容错处理 if (image == nil) { [self.operations removeObjectForKey:app.icon]; return; } // 演示网络延迟 //[NSThread sleepForTimeInterval:2.0]; // 线程间通信 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ // cell.imageView.image = image; // 刷新一行(会重新调用cellForRow方法) [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight]; }]; // 把图片保存到内存缓存 [self.images setObject:image forKey:app.icon]; // 写数据到沙盒 [iconData writeToFile:fullPath atomically:YES]; // NSLog(@"%@",fullPath); NSLog(@"%ld--下载--",indexPath.row); // 移除图片的下载操作 [self.operations removeObjectForKey:app.icon]; }]; // 添加操作到操作缓存中 [self.operations setObject:downloadImage forKey:app.icon]; // 把操作添加到队列中 [self.queue addOperation:downloadImage]; } } } // 返回cell return cell;}@end
沙盒
Documents: 会备份,不允许
tmp: 临时路径(随时会被删除)
Libray
Preferences: 偏好设置,保存账号密码
caches: 缓存文件
- iOS多线程 一一 多图下载程序
- iOS多线程 一一 多线程简介
- iOS多线程 一一 多线程篇
- ios多图下载(多线程知识)
- iOS多线程 一一 SDWebImage框架的基本使用
- iOS开发网络篇 一一 NSURLConnection-文件下载
- iOS开发网络篇 一一 NSURLConnection-大文件断点下载
- iOS开发网络篇 一一 NSURLSessionDownloadTask实现文件下载
- iOS开发网络篇 一一 NSURLSessionDownloadTask实现断点下载
- java多线程下载程序
- IOS多线程下载图片
- ios多线程断点下载
- iOS--多线程断点下载
- iOS 一一 通知机制
- iOS 一一 Block详解
- iOS 一一 数据存储
- iOS 一一 触摸事件
- iOS 一一 核心动画
- java网络编程之Netty第一个程序(四)
- (七)java并发编程synchronized+volatile(安全初始化模式实例)
- codeforces Educational Codeforces Round 33 (Rated for Div. 2)
- codeforces Educational Codeforces Round 33 (Rated for Div. 2)B
- (九)java并发编程--java.lang.TheadLocal
- iOS多线程 一一 多图下载程序
- nginx+tomcat+memcached (msm)实现 session同步复制
- TreeUtil 数据库菜单生成无限级树形结构
- Java笔记2
- 单层感知机及其代码实现
- Linux下lshw,lsscsi,lscpu,lsusb,lsblk硬件查看命令
- TCP与UDP的区别
- Function
- IP协议