应用背景:
如果是简单的应用层开发
,用GCD就足够了,NSThread,PThread,GCD,NSOperation,
但是如果是类似断点续传这样复杂业务
的时候,就需要使用NSOperation,控制更加灵活
。
知识图谱:
我将从5个方面逐步的分析NSOperation:
Demo地址: github.com/tanghaitao/…
1.NSOperation初体验
Demo地址:
github.com/tanghaitao/…
从苹果官方文档可以看出,NSOperation需要注意以下三点:
- NSOperation本身是一个抽象类,不能实例化。
- 需要把任何添加到它的子类NSInvocationOperation或NSBlockOperation。
- 要执行任务,需要添加到队列NSOperationQueue才能执行任务。
总结: 不能直接用NSOperation --- 事务 () + queue = 把事务添加到队列 ---> 然后在新的线程上执行
NSInvationOperation
- (void)demo1{
// 不能直接用NSOperation --- 事务 () + queue = 把事务添加到队列 ---> 然后去执行
// NSOperation
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(xixihha) object:nil];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 由系统调控 开辟新线程执行 NSThread == <NSThread: 0x6000024bd300>{number = 4, name = (null)}
[queue addOperation:op]; //这句和下面[op start] 不能同时调用
// 手动吊起
// [op start];//不用,立刻执行 NSThread == <NSThread: 0x60000382c1c0>{number = 1, name = main}
}
// 操作
- (void)xixihha{
NSLog(@"NSThread == %@",[NSThread currentThread]);
NSLog(@"123");
}
复制代码
NSBlockOperation
- (void)demo2{
// 操作优先级
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"NSThread == %@",[NSThread currentThread]);
// 并发
[NSThread sleepForTimeInterval:1];//延迟1秒
NSLog(@"执行123任务");
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"执行任务更新UI");
[NSThread sleepForTimeInterval:10];
NSLog(@"更新UI");
}];
// 不添加下面这两行代码,执行完op队列中的任务,mainQueue中的任务并没有执行完1+5<10
// 添加下面这两行代码的话,执行完所有op队列中的任务以前mainQueue中的任务也已经执行完了1+13>10
// completionBlock是等任务执行完,大括号开始和结束'{}'
//[NSThread sleepForTimeInterval:13];
//NSLog(@"123");
}];
// CPU 调度的评率高
// op.qualityOfService =
[op addExecutionBlock:^{
NSLog(@"NSThread == %@",[NSThread currentThread]);
NSLog(@"执行456任务");
[NSThread sleepForTimeInterval:5];
NSLog(@"456");
}];
op.completionBlock = ^{
NSLog(@"执行完成任务");
[NSThread sleepForTimeInterval:1];
NSLog(@"完成");
};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 由系统调控
[queue addOperation:op];
// [op start];
// NSThread == <NSThread: 0x60000018c640>{number = 6, name = (null)}
}
复制代码
//打印线程的执行任务顺序不固定
NSThread == <NSThread: 0x6000030fedc0>{number = 5, name = (null)}
NSThread == <NSThread: 0x6000030fd900>{number = 4, name = (null)}//
执行456任务
执行123任务
执行任务更新UI//456在睡眠,所以先执行mainQueue
456//mainQueue睡眠更久,5秒后执行456
执行完成任务//op的任务都完成了,第5秒钟
完成//完成,第6秒
更新UI// 第10秒
复制代码
分析: 任务[op addExecutionBlock]
和任务blockOperationWithBlock:^{}
是并发的,执行任务顺序不固定,然后执行打印执行456任务
,再打印执行123任务
,由于主队列的任务是在第一个block中的线程中的,堵塞,所以需要等待
执行123任务执行完再打印
,更新UI
,其他的看上面的注释
[[NSOperationQueue mainQueue] addOperationWithBlock:^{} 必须等 任务1执行后'{'再执行,不一定执行完'}'。
op.completionBlock= ^{}必须等 op`添加到队列`的所有任务执行完,大括号开始和结束 ‘{}’,'{}'执行完毕,再执行
这里的队列是由系统调控
[queue addOperation:op];是不是主队列(mainQueue),不必等待mainQueue完成。
复制代码
2.NSOperation属性研究
Demo地址:
github.com/tanghaitao/…
2.1 maxConcurrentOperationCount
用NSOperation控制并发数
非常简单
,直接设置属性maxConcurrentOperationCount
GCD控制就相当复杂。
GCD控制最大并发数:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
//任务1
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@" --- %@ ---- 任务1",[NSThread currentThread]);
NSLog(@"执行任务1");
sleep(6);//【睡眠6秒】
NSLog(@"任务1完成");
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@" --- %@ ---- 任务2",[NSThread currentThread]);
NSLog(@"执行任务2");
sleep(5);// 【睡眠5秒】
NSLog(@"任务2完成");
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@" --- %@ ---- 任务3",[NSThread currentThread]);
NSLog(@"执行任务3");
NSLog(@"任务3完成");
dispatch_semaphore_signal(semaphore);
});
复制代码
NSOperation控制最大并发数:
/**
关于operationQueue的挂起,继续,取消
*/
- (void)demo1{
// GCD ---> 信号量 : 对于线程操作更自如 -- suspend cancel finish
// 多线程世界
self.queue.name = @"com.haitao";
self.queue.maxConcurrentOperationCount = 2;
for (int i = 0; i<10; i++) {
[self.queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"%@--%d",[NSThread currentThread],i);
}];
}
}
复制代码
begin <NSThread: 0x6000038e2fc0>{number = 6, name = (null)}--1
begin <NSThread: 0x6000038a8f00>{number = 3, name = (null)}--0
end <NSThread: 0x6000038e2fc0>{number = 6, name = (null)}--1
end <NSThread: 0x6000038a8f00>{number = 3, name = (null)}--0
begin <NSThread: 0x6000038a1040>{number = 4, name = (null)}--2
begin <NSThread: 0x6000038a8f00>{number = 3, name = (null)}--3
end <NSThread: 0x6000038a8f00>{number = 3, name = (null)}--3
end <NSThread: 0x6000038a1040>{number = 4, name = (null)}--2
begin <NSThread: 0x6000038a1040>{number = 4, name = (null)}--4
begin <NSThread: 0x6000038e2fc0>{number = 6, name = (null)}--5
end <NSThread: 0x6000038a1040>{number = 4, name = (null)}--4
end <NSThread: 0x6000038e2fc0>{number = 6, name = (null)}--5
begin <NSThread: 0x6000038a1040>{number = 4, name = (null)}--7
begin <NSThread: 0x6000038a8f00>{number = 3, name = (null)}--6
end <NSThread: 0x6000038a1040>{number = 4, name = (null)}--7
end <NSThread: 0x6000038a8f00>{number = 3, name = (null)}--6
begin <NSThread: 0x6000038a1040>{number = 4, name = (null)}--8
begin <NSThread: 0x6000038e2fc0>{number = 6, name = (null)}--9
end <NSThread: 0x6000038a1040>{number = 4, name = (null)}--8
end <NSThread: 0x6000038e2fc0>{number = 6, name = (null)}--9
复制代码
上图的输出日志: 每次执行2个任务,begin begin end end
- (IBAction)pauseOrContinue:(id)sender {
// 下载任务 ---> task ---> 挂起
// 继续 ---->
// 打断 ---> 后台
self.queue.suspended = !self.queue.isSuspended;
[self.pauseOrContinueBtn setTitle:self.queue.suspended?@"继续":@"暂停" forState:UIControlStateNormal];
if (self.queue.operationCount == 0) {
NSLog(@"没有操作执行");
return;
}
if (self.queue.suspended) {
NSLog(@"当前挂起来了");
}else{
NSLog(@"执行....");
}
// self.queue
}
复制代码
begin <NSThread: 0x600000888a00>{number = 8, name = (null)}--1
begin <NSThread: 0x600000880780>{number = 6, name = (null)}--0
end <NSThread: 0x600000888a00>{number = 8, name = (null)}--1
end <NSThread: 0x600000880780>{number = 6, name = (null)}--0
begin <NSThread: 0x60000089cd80>{number = 7, name = (null)}--2
begin <NSThread: 0x600000888a00>{number = 8, name = (null)}--3
前挂起来了
end <NSThread: 0x600000888a00>{number = 8, name = (null)}--3
end <NSThread: 0x60000089cd80>{number = 7, name = (null)}--2
复制代码
self.queue.suspended = !self.queue.isSuspended;
挂起后,正在执行的任务不会马上挂起或者取消,等待执行完再停止后续添加的新任务。
2.3 addDependency
控制任务的执行顺序‘{’
, 会堵塞被依赖的线程,知道依赖的任务执行完成再会执行下一个
,类似 op.completionBlock = ^{}
- (void)demo2{
self.queue.maxConcurrentOperationCount = 2;
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"begin 请求token");
[NSThread sleepForTimeInterval:0.5];
NSLog(@"end 请求token");
}];
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"begin 拿着token,请求数据1");
[NSThread sleepForTimeInterval:1.5];
NSLog(@"end 拿着token,请求数据1");
}];
NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"begin 拿着数据1,请求数据2");
[NSThread sleepForTimeInterval:0.5];
NSLog(@"end 拿着数据1,请求数据2");
}];
//[self.queue addOperation:bo1];
//依赖
[bo2 addDependency:bo1];
[bo3 addDependency:bo2];
[self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:NO];
NSLog(@"执行完了?我要干其他事");
}
复制代码
执行完了?我要干其他事//waitUntilFinished:NO,如YES,就堵塞,最后打印
begin 请求token
end 请求token
begin 拿着token,请求数据1
end 拿着token,请求数据1
begin 拿着数据1,请求数据2
end 拿着数据1,请求数据2
复制代码
3. NSOperation缓存方案和注意事项
Demo地址: github.com/tanghaitao/…
__weak typeof(self) weakSelf = self;
self.viewModel = [[KCViewModel alloc] initWithBlock:^(id data) {
[weakSelf.dataArray addObjectsFromArray:data];
[weakSelf.collectionView reloadData];
} fail:nil];
//最好是dispatch_after子线程延迟操作,不要影响主线程,影响启动加载
复制代码
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 重复下载
NSLog(@"下载图片: %@",model.title);
NSURL *url = [NSURL URLWithString:model.imageUrl];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];//data为nil不会奔溃
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
复制代码
打印输出:
2021-05-26 10:59:05.004338+0800 003---自定义NSOperation[14013:11296141] 下载图片: 典雅的教堂
2021-05-26 10:59:05.005366+0800 003---自定义NSOperation[14013:11296143] 下载图片: 西湖美女
2021-05-26 10:59:05.005908+0800 003---自定义NSOperation[14013:11296146] 下载图片: 优美海景
2021-05-26 10:59:05.006446+0800 003---自定义NSOperation[14013:11296144] 下载图片: 微信壁纸
2021-05-26 10:59:05.006990+0800 003---自定义NSOperation[14013:11296149] 下载图片: 高清无码美女
2021-05-26 10:59:05.007565+0800 003---自定义NSOperation[14013:11296147] 下载图片: 深沉匹若曹
2021-05-26 10:59:05.008052+0800 003---自定义NSOperation[14013:11296142] 下载图片: 毛笔执念
2021-05-26 10:59:05.008553+0800 003---自定义NSOperation[14013:11296151] 下载图片: 简约蒲苇
2021-05-26 10:59:05.150593+0800 003---自定义NSOperation[14013:11296154] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
2021-05-26 10:59:07.123287+0800 003---自定义NSOperation[14013:11296151] 下载图片: 笑脸路飞
2021-05-26 10:59:07.141981+0800 003---自定义NSOperation[14013:11296142] 下载图片: 高清玉米棒子
2021-05-26 10:59:07.158524+0800 003---自定义NSOperation[14013:11296154] 下载图片: 五彩星星
2021-05-26 10:59:07.175219+0800 003---自定义NSOperation[14013:11296150] 下载图片: 欧豪篮球
2021-05-26 10:59:07.220150+0800 003---自定义NSOperation[14013:11296168] 下载图片: 渣男
2021-05-26 10:59:07.237240+0800 003---自定义NSOperation[14013:11296155] 下载图片: 姑娘走了
2021-05-26 10:59:07.253587+0800 003---自定义NSOperation[14013:11296166] 下载图片: 简约dog
2021-05-26 10:59:07.269462+0800 003---自定义NSOperation[14013:11296143] 下载图片: 简约dog
2021-05-26 10:59:07.302763+0800 003---自定义NSOperation[14013:11296170] 下载图片: 简约dog
2021-05-26 10:59:07.319611+0800 003---自定义NSOperation[14013:11296147] 下载图片: 简约dog
2021-05-26 10:59:07.352682+0800 003---自定义NSOperation[14013:11296171] 下载图片: 可爱超人
2021-05-26 10:59:07.371843+0800 003---自定义NSOperation[14013:11296141] 下载图片: 可爱超人
2021-05-26 10:59:07.789925+0800 003---自定义NSOperation[14013:11296143] 下载图片: 优美海景
2021-05-26 10:59:07.807064+0800 003---自定义NSOperation[14013:11296166] 下载图片: 微信壁纸
2021-05-26 10:59:07.827490+0800 003---自定义NSOperation[14013:11296170] 下载图片: 典雅的教堂
2021-05-26 10:59:07.840837+0800 003---自定义NSOperation[14013:11296172] 下载图片: 西湖美女
2021-05-26 10:59:08.311868+0800 003---自定义NSOperation[14013:11296154] 下载图片: 简约dog
2021-05-26 10:59:08.328546+0800 003---自定义NSOperation[14013:11296172] 下载图片: 简约dog
2021-05-26 10:59:08.345750+0800 003---自定义NSOperation[14013:11296144] 下载图片: 可爱超人
2021-05-26 10:59:08.362655+0800 003---自定义NSOperation[14013:11296167] 下载图片: 可爱超人
复制代码
重复下载,譬如:下载图片: 典雅的教堂
执行了多次,浪费内存。
解决方案
3.1 从模型加载数据
// 从模型加载数据
if (model.image) {
NSLog(@"从模型加载数据");
cell.imageView.image = model.image;
return cell;
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
model.image = image;
}];
2021-05-26 11:01:30.524417+0800 003---自定义NSOperation[14094:11306727] 下载图片: 典雅的教堂
2021-05-26 11:01:30.525197+0800 003---自定义NSOperation[14094:11306723] 下载图片: 西湖美女
2021-05-26 11:01:30.525595+0800 003---自定义NSOperation[14094:11306728] 下载图片: 优美海景
2021-05-26 11:01:30.526080+0800 003---自定义NSOperation[14094:11306725] 下载图片: 微信壁纸
2021-05-26 11:01:30.526448+0800 003---自定义NSOperation[14094:11306722] 下载图片: 高清无码美女
2021-05-26 11:01:30.526841+0800 003---自定义NSOperation[14094:11306724] 下载图片: 深沉匹若曹
2021-05-26 11:01:30.527250+0800 003---自定义NSOperation[14094:11306731] 下载图片: 毛笔执念
2021-05-26 11:01:30.527632+0800 003---自定义NSOperation[14094:11306733] 下载图片: 简约蒲苇
2021-05-26 11:01:30.669017+0800 003---自定义NSOperation[14094:11306738] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
2021-05-26 11:03:40.483158+0800 003---自定义NSOperation[14094:11306728] 下载图片: 笑脸路飞
2021-05-26 11:03:40.498987+0800 003---自定义NSOperation[14094:11315435] 下载图片: 高清玉米棒子
2021-05-26 11:03:40.515254+0800 003---自定义NSOperation[14094:11315433] 下载图片: 五彩星星
2021-05-26 11:03:40.531969+0800 003---自定义NSOperation[14094:11315441] 下载图片: 欧豪篮球
2021-05-26 11:03:40.561970+0800 003---自定义NSOperation[14094:11315434] 下载图片: 渣男
2021-05-26 11:03:40.578452+0800 003---自定义NSOperation[14094:11315445] 下载图片: 姑娘走了
2021-05-26 11:03:40.612491+0800 003---自定义NSOperation[14094:11315440] 下载图片: 简约dog
2021-05-26 11:03:40.629275+0800 003---自定义NSOperation[14094:11315447] 下载图片: 简约dog
2021-05-26 11:03:40.661915+0800 003---自定义NSOperation[14094:11315446] 下载图片: 简约dog
2021-05-26 11:03:40.678988+0800 003---自定义NSOperation[14094:11315444] 下载图片: 简约dog
2021-05-26 11:03:40.728374+0800 003---自定义NSOperation[14094:11315450] 下载图片: 可爱超人
2021-05-26 11:03:40.744980+0800 003---自定义NSOperation[14094:11315449] 下载图片: 可爱超人
2021-05-26 11:03:41.299598+0800 003---自定义NSOperation[14094:11306166] 从模型加载数据
2021-05-26 11:03:41.315792+0800 003---自定义NSOperation[14094:11306166] 从模型加载数据
2021-05-26 11:03:41.331618+0800 003---自定义NSOperation[14094:11306166] 从模型加载数据
2021-05-26 11:03:41.348666+0800 003---自定义NSOperation[14094:11306166] 从模型加载数据
2021-05-26 11:03:42.868586+0800 003---自定义NSOperation[14094:11306166] 从模型加载数据
2021-05-26 11:03:42.885300+0800 003---自定义NSOperation[14094:11306166] 从模型加载数据
2021-05-26 11:03:42.901959+0800 003---自定义NSOperation[14094:11315445] 下载图片: 笑脸路飞
2021-05-26 11:03:42.918589+0800 003---自定义NSOperation[14094:11306728] 下载图片: 高清玉米棒子
2021-05-26 11:03:42.951874+0800 003---自定义NSOperation[14094:11315444] 下载图片: 五彩星星
2021-05-26 11:03:42.968557+0800 003---自定义NSOperation[14094:11306166] 从模型加载数据
复制代码
从模型加载数据,有个小bug: 在页面加载后滑动页面,从模型加载数据
,接着点击模拟器
,在菜单栏上选择Debug
– Simulate Memory Warning
后,会调用内存警告函数didReceiveMemoryWarning
- (void)didReceiveMemoryWarning{
NSLog(@"收到内存警告,你要清理内存了!!!");
}
复制代码
2021-05-26 11:12:10.189135+0800 003---自定义NSOperation[14535:11382577] 从模型加载数据
2021-05-26 11:12:10.204499+0800 003---自定义NSOperation[14535:11382577] 从模型加载数据
2021-05-26 11:12:10.466714+0800 003---自定义NSOperation[14535:11383187] 下载图片: 笑脸路飞
2021-05-26 11:12:10.483011+0800 003---自定义NSOperation[14535:11383199] 下载图片: 高清玉米棒子
2021-05-26 11:12:13.340177+0800 003---自定义NSOperation[14535:11382577] 收到内存警告,你要清理内存了!!!
复制代码
此时如果将模型中的数据删除的话,这样是不可取的,因为每次删除内存,耗费内存,对手机电量造成极大影响
。
正确的处理方法就是下面的:
3.2 从内存,磁盘加载数据
关于内存和磁盘的问题:
内存就是存储到字典,数组等oc或c对象,链表中,页面退出或者程序退出后相应内存就会被删除,`一般通过key读取`
磁盘就是沙盒中保存的plist,file等文件,读取速度比内存慢很多,`通过路径读取`
// 内存 , 磁盘 首先加载内存 (快) ---> 磁盘 (保存一份到内存) ---> 下载 保存内存和磁盘
复制代码
// 从内存加载数据
UIImage *cacheImage = self.imageCacheDict[model.imageUrl];
if (cacheImage) {
NSLog(@"从内存加载数据");
cell.imageView.image = cacheImage;
return cell;
}
// 磁盘加载
cacheImage = [UIImage imageWithContentsOfFile:[model.imageUrl getDowloadImagePath]];
if (cacheImage) {
NSLog(@"从磁盘加载数据");
cell.imageView.image = cacheImage;
//磁盘找到后保存到内存,方便下次直接从内存中读取
[self.imageCacheDict setObject:cacheImage forKey:model.imageUrl];
return cell;
}
/**
下载图片的路径
@return MD5加密的图片下载地址
*/
- (NSString *)getDowloadImagePath{
// url ---> 唯一性,url相同,根据path后不唯一,md5可以保证唯一
// url 很长 ;md5可以保证长度 固定
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *md5Str = [self kc_md5String];
return [cachePath stringByAppendingPathComponent:md5Str];
}
/**
MD5 加密
@return 返回MD5加密数据
*/
- (NSString *)kc_md5String{
// const char *cStr = [self UTF8String];
// unsigned char result[16];
// CC_MD5(cStr, (CC_LONG)strlen(cStr), result);
// return [NSString stringWithFormat:
// @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
// result[0], result[1], result[2], result[3],
// result[4], result[5], result[6], result[7],
// result[8], result[9], result[10], result[11],
// result[12], result[13], result[14], result[15]
// ];
const char *str = self.UTF8String;
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSURL *keyURL = [NSURL URLWithString:self];
NSString *ext = keyURL ? keyURL.pathExtension : self.pathExtension;
return [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7],
r[8], r[9], r[10],r[11], r[12], r[13], r[14], r[15],
ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
}
复制代码
存储数据的时候,一定要保证当前地址是唯一的。
/Users/hai/Library/Developer/CoreSimulator/Devices/DFFAFFED-D649-4A68-9DC5-5CC46BEFBB37/data/Containers/Data/Application/ED0A679B-1B7B-4E5C-B733-55795509F611 复制代码
http://e.hiphotos.baidu.com/image/h%3D300/sign=0708e32319ce36d3bd0485300af23a24/fcfaaf51f3deb48fd0e9be27fc1f3a292cf57842.jpg
md5Str=== e69dc1165d81d02165c1c19efe6102f8.jpg
复制代码
http://e.hiphotos.baidu.com/image/h%3D300/sign=0708e32319ce36d3bd0485300af23a24/fcfaaf51f3deb48fd0e9be27fc1f3a292cf57842.jpg
md5Str=== e69dc1165d81d02165c1c19efe6102f8.jpg
复制代码
上述说法有误。
一般第三方网络框架,网络请求
的话,会执行NSString *URLIdentifier = request.URL.absoluteString
,即忽略掉服务器的域名
,只会执行图片下载地址的url相对地址
,如果更换服务器域名
,相对地址没变
,就会导致不能下载服务器最新域名对应的图片
,直接使用md5的话
,是绝对地址的md5
,这样就保证了地址唯一性.
https://www.baidu.com/photos/1.jpg
https://www.sina.com/photos/1.jpg
request.URL.absoluteString = photos/1.jpg
request.URL.absoluteString = photos/1.jpg
//使用md5后
absoluteString([URL+md5(URL)]) = photos/1.jpg + md5(https://www.baidu.com/photos/1.jpg)
absoluteString([URL+md5(URL)]) = photos/2.jpg + md5(https://www.sina.com/photos/1.jpg)`
复制代码
具体情况根据缓存cache.db文件存储的key来决定。
,上面的讲解可能跟具体情况不一致,但是原理都是类似的。这也是使用第三方的好处,做了很多额外的工作。
3.3 缓存下载任务
3.2 从内存和磁盘中读取的话,有个小bug。
下面是演示过程:
打开模拟器,打开Charles,记住要勾选MacOS Proxy
,设置带宽,如下图
在网速很差或者低端机, 图片下载一直没回来或者写入磁盘非常慢,一直在队列上添加任务,导致内存剧增,达到一定值,程序就会闪退。网络延迟默认是60s,60s内没有请求完,页面一直滑动就会一直添加任务。
解决办法是 :相同的操作只执行一次,不要重新执行下载任务。
if (self.operationDict[model.imageUrl]) {
NSLog(@"兄弟,稍微一等< %@已经提交下载了",model.title);
return cell;
}
复制代码
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
//反复执行的任务
}];
// 将下载事务添加到队列
[self.queue addOperation:op];
// 将下载事务做记录
[self.operationDict setObject:op forKey:model.imageUrl];
复制代码
// 下载完成操作 从记录清除,否则如果下载完成但是只下载一半,就不会重新下载了
[self.operationDict removeObjectForKey:model.imageUrl];
复制代码
4. 自定义NSOperation
3.中的流程非常繁琐,每次执行一个图片下载的页面,需要写那么多代码,这里可以封装自定义的NSOperation.
参考SdWebImage
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:model.imageUrl]];
复制代码
Demo地址:
github.com/tanghaitao/…
思路:
封装遵循一个原则: 单一原则
:
ImageView分类, 调度ImageView是Manager,下载是NSOperation
1. UIImageView+KCWebCache 分类
[cell.imageView kc_setImageWithUrlString:model.imageUrl title:model.title indexPath:indexPath];
判断URL,当前正在执行的事务记录等
2.KCWebImageManager 单例
管理所有缓存策略,最大并发数,内存警告
// 只要调用单利,就会来到这里 那么我就可以在这里做一系列的初始化
- (instancetype)init{
if (self=[super init]) {
self.queue.maxConcurrentOperationCount = 2;
// 注册内存警告通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(memoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
3. KCWebImageDownloadOperation
自定义Operation执行图片下载任务,回调执行结果
https://developer.apple.com/library/archive/navigation/ 搜索NSOperation找到Sample
For non-concurrent operations, you typically override only one method
- main
If you are creating a concurrent operation, you need to override the following methods and properties at a minimum:
- start
- asynchronous
- executing
- finished
非并发的重新main就好了
并发的需要执行start等函数做处理
// 因为父类的属性是Readonly的,重载时如果需要setter的话则需要手动合成。
复制代码
- (void)start{
// 延长生命周期
// 大型循环
// 自己创建管理线程
@autoreleasepool{
[_lock lock];//线程安全
[_lock unlock];
}
}
复制代码
5. NSOperation自定义总结
//对当前下载图片判断,是否需要创建操作,相同url,有一个执行完成就不需要再去下载了。
if (self.operationDict[urlString]) {
NSLog(@"正在下载,让子弹飞会 %@",title);
NSLog(@"正在下载的回调Block %@的%@",title,completeHandle);
NSMutableArray *mArray = self.handleDict[urlString];
if (mArray == nil) {
mArray = [NSMutableArray arrayWithCapacity:1];
}
[mArray addObject:completeHandle];
//相同url的第1个任务,一开始数组个数为1,completeHandle1
//相同url的第2个任务, 数组个数为2,completeHandle1,completeHandle2
[self.handleDict setObject:mArray forKey:urlString];
return;
}
// 下面就是创建操作 下载 --- 自定义
KCWebImageDownloadOperation *downOp = [[KCWebImageDownloadOperation alloc] initWithDownloadImageUrl:urlString completeHandle:^(NSData *imageData,NSString *kc_urlString) {
UIImage *downloadImage = [UIImage imageWithData:imageData];
if (downloadImage) {
[self.imageCacheDict setObject:downloadImage forKey:urlString];
[self.operationDict removeObjectForKey:urlString];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
completeHandle(downloadImage,kc_urlString);
//去取回调
//回调相同url的所有任务结果
if (self.handleDict[kc_urlString]) {
NSMutableArray *mArray = self.handleDict[kc_urlString];
for (KCCompleteHandleBlock handleBlock in mArray) {
handleBlock(downloadImage,kc_urlString);
}
[self.handleDict removeObjectForKey:urlString];
}
}];
}
} title:title];
复制代码
[mArray addObject:completeHandle];
[self.handleDict setObject:mArray forKey:urlString];
复制代码
如果界面上有多个相同的url任务,相同url,有一个执行完成就不需要再创建新的下载任务了。
if (self.kc_urlString && self.kc_urlString.length>0 && ![self.kc_urlString isEqualToString:urlString]) {
// 下载ing 没有回来(图片太大 网速太慢) 取消(已死), 之前线程中的任务回调
// NSLog(@"取消之前的下载操作 %@",title);
NSLog(@"取消%@之前的下载操作:%@---%@ \n%@---%@",indexPath,self.kc_title,title,self.kc_urlString,urlString);
[[KCWebImageManager sharedManager] cancelDownloadImageWithUrlString:self.kc_urlString];
}
//新操作要开始下载 就要记录
self.kc_urlString = urlString;
self.kc_title = title;
self.image = nil;
[[KCWebImageManager sharedManager] downloadImageWithUrlString:urlString completeHandle:^(UIImage *downloadImage,NSString *urlString) {
//下载完成 要制空
if ([urlString isEqualToString:self.kc_urlString]) {
self.kc_urlString = nil;
self.kc_title = nil;
self.image = downloadImage;
}
} title:title];
- (void)setKc_urlString:(NSString *)kc_urlString{
/**
1: 绑定的对象
2: 关联键,通过这个键去找
3: 值
4: 关联策略
*/
objc_setAssociatedObject(self, kcAssociatedKey_imageUrlString, kc_urlString, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)kc_urlString{
return objc_getAssociatedObject(self, kcAssociatedKey_imageUrlString);
}
复制代码
通过runtime objc_setAssociatedObject
,objc_getAssociatedObject
去监听当前任务属性 kc_urlString
的变化,如果没有请求完成,则 kc_urlString
一直不为nil
表示下载ing 没有回来(图片太大 网速太慢),需要 取消该任务 (已死) cancelDownloadImageWithUrlString:self.kc_urlString