在iOS开发中经常会遇到下载好多较大图片并且在二级界面展示到UIImageView的情况,例如探探中多卡片的图片展示。
当然如果将图片下载这种极耗时的操作放在主线程操作会造成程序假死的状况,所以考虑使用在多线程异步加载并且添加线程间依赖的方式,尽可能好的提高用户体验。
在这之前需要一些知识储备:1.线程依赖。2.沙盒存储
1.线程依赖.
目前在 iOS 和 OS X 中有两套先进的同步 API 可供我们使用:NSOperation 和 GCD 。其中 GCD 是基于 C 的底层的 API ,而 NSOperation 则是 GCD 实现的 Objective-C API。 虽然 NSOperation 是基于 GCD 实现的,我们可以用NSOperation 轻易的实现一些 GCD 要写大量代码的事情。
操作队列(operation queue)是由 GCD 提供的一个队列模型的 Cocoa 抽象。GCD 提供了更加底层的控制,而操作队列则在 GCD 之上实现了一些方便的功能,这些功能对于 app 的开发者来说通常是最好最安全的选择。
//NSOperationQueue 线程之前添加依赖操作
-(void)dependency{
/**
假设有A、B、C三个操作,要求:
1. 3个操作都异步执行
2. 操作C依赖于操作B
3. 操作B依赖于操作A
*/
//创建一个队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//可开辟线程的最大数量
queue.maxConcurrentOperationCount = 3;
//创建三个任务
NSBlockOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"A任务当前线程为:%@", [NSThread currentThread]);
}];
NSBlockOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"B任务当前线程为:%@", [NSThread currentThread]);
}];
NSBlockOperation *operationC = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"C任务当前线程为:%@", [NSThread currentThread]);
}];
//设置三个任务相互依赖
// operationB 任务依赖于 operationA
[operationB addDependency:operationA];
// operationC 任务依赖于 operationB
[operationC addDependency:operationB];
//添加操作到队列中(自动异步执行任务,并发)
[queue addOperation:operationA];
[queue addOperation:operationB];
[queue addOperation:operationC];
}
2.沙盒存储
iOS 数据存储方式不再过多累述
直接上代码:
//1.获取本地cache文件路径
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//2.根据文件URL最后文件名作为文件名保存到本地 -> 文件名
NSString *imageFilePath = [cachePath stringByAppendingPathComponent:imageName];
//3.写入文件
[UIImagePNGRepresentation(image) writeToFile:imageFilePath atomically:YES];
储备完成,言归正传》》
无论做什么先明确目标:
网络请求成功得到众多图片路径,需要下载并且展示在二级界面中?
实现思路:
1.NSOperationQueue 多线程(添加依赖)将下载图片操作放在不同线程中执行。
2.如何添加依赖:跳转操作需要依赖于所有下载任务执行完毕后进行
3.将下载的图片数据保存在沙盒中(缓存图片一般放在Cache文件夹中,可以以后清除缓存)- 此处需要保证沙盒中不存在此数据
4.跳转,展示多图界面
实现代码:
首先添加NSOperationQueue属性,并懒加载
/** 创建一个队列 */
@property (nonatomic, strong) NSOperationQueue *queue;
@end
@implementation RadarController
//下载队列
-(NSOperationQueue *)queue{
if (_queue == nil) {
_queue = [[NSOperationQueue alloc] init];
//可开辟线程最大值
[_queue setMaxConcurrentOperationCount:10];
}
return _queue;
}
网络请求得到图片路径,使用效率最高apple原生NSJSONSerialization的解析JSON数据
//JSON 解析 苹果原生效率最高
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
NSLog(@"请求成功... %@",result);
NSDictionary *dataDict = [result objectForKey:@"result"];
最终的跳转操作(界面跳转)
#pragma mark - 跳转到二级界面展示所下载的图片
- (void)startUpdatingRadar {
typeof(self) __weak weakSelf = self;
//延时操作,在0.1秒后在主线程进行push操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
DiffusionController *diffusion = [[DiffusionController alloc] initWithJokeImageArr:_jokeImageDataArrM];
[diffusion setHidesBottomBarWhenPushed:YES];
[self.navigationController pushViewController:diffusion animated:YES];
});
}
图片下载和沙盒保存方法:传入图片路径和图片名(沙盒中保存的文件名)的下载进而保存沙盒的操作
#pragma mark - 根据请求到的图片路径网络下载图片
//需要将此下载操作放在异步线程中进行!
//根据图片路径URL -> 添加下载任务(异步线程)-> 将下载的图片保存到本地 -> 以便在二级界面直接从文件中读取
-(void)downloadImageWithURL:(NSString *)URLStr WithImageName:(NSString *)imageName{
//使用线程依赖
@autoreleasepool {
NSLog(@"当期啊线程编号:%@",[NSThread currentThread]);
//子线程里面的runloop默认不开启,也就意味着不会自动创建自动释放池,子线程里面autorelease的对象 就会没有池子释放。也就一位置偶棉没有办法进行释放造成内存泄露,所以需要手动创建
//1.获取本地cache文件路径
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//2.根据文件URL最后文件名作为文件名保存到本地 -> 文件名
NSString *imageFilePath = [cachePath stringByAppendingPathComponent:imageName];
//如果当前文件名在cache文件夹中不存在,写入文件
if (![self isFileExist:imageName]) {
//下载图片
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:URLStr]];
//初始化图片
UIImage *image = [UIImage imageWithData:imageData];
//将下载的图片保存到本地
//3.写入文件
// [UIImagePNGRepresentation(image) writeToFile:imageFilePath atomically:YES];
//或者->(此方法会减少缓存大小,但是图片会不清晰)
[UIImageJPEGRepresentation(image, 0.8) writeToFile:imageFilePath atomically:YES];
}
}
}
//判断文件是否已经在沙盒中已经存在?
-(BOOL) isFileExist:(NSString *)fileName
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
NSString *filePath = [path stringByAppendingPathComponent:fileName];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL result = [fileManager fileExistsAtPath:filePath];
NSLog(@"这个文件已经存在?:%@",result?@"存在":@"不存在");
return result;
}
核心代码,所有下载任务执行完毕后执行主任务->
//创建主任务-跳转页面
__block NSBlockOperation *jumpMainOperaion = [NSBlockOperation blockOperationWithBlock:^{
[self startUpdatingRadar];
}];
//模型数组
_jokeImageDataArrM = [[NSMutableArray alloc] init];
//遍历所有图片路径,并添加到下载队列中
for (NSDictionary *imageDict in (NSArray *)[dataDict objectForKey:@"data"]) {
JokeImageData *jokeImageData = [[JokeImageData alloc] init];
[jokeImageData setContent:[imageDict objectForKey:@"content"]];
NSString *imageUrl = [imageDict objectForKey:@"url"];
[jokeImageData setUrl:imageUrl];
NSString *imageName = [imageDict objectForKey:@"hashId"];
[jokeImageData setHashId:imageName];
[jokeImageData setUpdatetime:[imageDict objectForKey:@"updatetime"]]
;
//异步线程下载
//防止循环引用
__weak typeof(self) weakSelf = self;
currentBlockOperation = [NSBlockOperation blockOperationWithBlock:^{
//图片下载操作
[weakSelf downloadImageWithURL:imageUrl WithImageName:imageName];
}];
//添加依赖
[jumpMainOperaion addDependency:currentBlockOperation];
//添加到线程队列
[self.queue addOperation:currentBlockOperation];
//模型数组
[_jokeImageDataArrM addObject:jokeImageData];
}
//将最后的操作最后添加进队列
[self.queue addOperation:jumpMainOperaion];