NSOperation
- 是一个 抽象类 (The NSOperation class is an abstract class)
无法直接使用,因为只有方法声明没有实现
可使用它的子类:NSIvocationOperation、NSBlockOperation(较常用)
NSOperationQueue
- 队列(默认并发)
- 核心:将操作添加到队列里
- 常用属性&方法
- 最大并发数(队列最多以多少线程进行操作)
queue.maxConcurrentOperationCount = 2;
- 队列暂停&继续(队列暂停后可以添加任务进队列,但是不会执行)
queue.suspended = YES;
- 取消全部操作
[queue cancelAllOperations];
- 最大并发数(队列最多以多少线程进行操作)
NSInvocationOperation
- iOS2.0推出的
- 基于NSTread封装
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self demo2];
}
-(void)demo2{
NSInvocationOperation *io = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo:) object:@0];
//没有添加到队列里,直接使用start则是同步操作,在当前线程执行,不开辟新线程
[io start];
}
-(void)demo1{
//1. 创建队列
//NSOperationQueue是一个并发队列
NSOperationQueue *queue = [NSOperationQueue new];
for (int i = 0; i<5; i++) {
//2. 创建操作
//NSInvocationOperation默认是异步操作(任务)
NSInvocationOperation *io = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo:) object:@(i)];
//3. 将操作添加到队列里
[queue addOperation:io];
}
}
-(void)demo:(NSNumber *)num{
NSLog(@"%@ %@",num,[NSThread currentThread]);
}
demo2中,调用start方法,操作会在 当前线程 执行
NSBlockOperation
- iOS4.0推出的
- 基于GCD封装
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self demo2];
}
-(void)demo3{
NSBlockOperation *bk = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[bk start];
}
//无法取到操作对象
//线程通信
-(void)demo2{
//异步操作,并发队列
[_queue addOperationWithBlock:^{//Block里是1个操作,在同个线程里执行
for (int i=0; i<5; i++) {
NSLog(@"假装在下载%d...%@",i,[NSThread currentThread]);
}
NSString *str = @"123";
//主队列进行更新
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"假装在更新%@...%@",str,[NSThread currentThread]);
}];
}];
}
//可以取到操作对象
-(void)demo1{
//异步操作
NSBlockOperation *bk1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%@",[NSThread currentThread]);
}];
NSBlockOperation *bk2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
//并行队列
[_queue addOperation:bk1];
[_queue addOperation:bk2];
}
- 常用属性&方法
- 服务质量
op1.qualityOfService = NSQualityOfServiceUserInteractive;
- 优先级
op1.queuePriority = NSOperationQueuePriorityVeryHigh;
- 添加依赖
[op2 addDependency:op1];
- 取消依赖
[op2 removeDependency:op1];
- 服务质量
异步下载图片+缓存管理
- 要点:
- 子线程下载图片, 主线程更新UI
- 内存缓存池(NSMutableDictionary)
- 操作缓存池(操作完成前需移除)
- cell重用后操作刚好完成, 导致错误刷新问题(reloadRowsAtIndexPaths)
- 沙盒缓存(1. 在内存缓存后判断 2. 沙盒缓存后写入内存缓存)
- 内存警告处理(清除 图片&操作缓存池)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MYTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"mycell" forIndexPath:indexPath];
MYAppInfo *model = _appList[indexPath.row];
cell.titleLbl.text = model.name;
cell.detailLbl.text = model.download;
//1.判断内存里是否有缓存图片,如果有则直接加载,提高加载效率,减少网络使用
if (_ramImg[model.icon]) {
NSLog(@"从内存加载");
cell.iconView.image = _ramImg[model.icon];
return cell;
}
UIImage *cellImg = [UIImage imageWithContentsOfFile:[model.icon cachesPath]];
//2.判断沙盒里是否有缓存过的图片,有则从沙盒直接加载
if (cellImg) {
NSLog(@"从沙盒加载");
cell.iconView.image = cellImg;
[_ramImg setObject:cellImg forKey:model.icon];
return cell;
}
//程序启动,没有内存和沙盒缓存的情况下,先给一个占位图,避免图片即使加载好,但是图片布局没有刷新,图片没有大小无法显示问题
cell.iconView.image = [UIImage imageNamed:@"user_default"];
//3.判断当前cell的图片是否已经有操作在执行,如果没有才新创建操作,有的话就不继续,解决延迟问题1
if (_opList[model.icon]) {
return cell;
}
//4.创建操作,从网络下载图片
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"从网络加载");
if (indexPath.row > 11) {
//模拟网络请求数据时延迟,此时来回滚动会出现两个问题
//1.延迟时间较长,滚动tabelView就不断进入数据源方法,而此时图片数据在内存里没有缓存,就会不断进入创建操作的方法里,重复创建操作
//2.cell重用问题,cell重用后之前的操作可能因为网络延迟,没有及时将刷新到之前的cell里,之前的cell就被推出后重用,此时刚好加载完之前的数据,重用cell的操作就会对当前cell进行刷新
[NSThread sleepForTimeInterval:5];
}
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:model.icon]];
UIImage *iconImg = [UIImage imageWithData:data];
//6.下载完成,线程通讯,回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// cell.iconView.image = iconImg;
if (iconImg) {
//7.将加载好的图片写入内存缓存池,避免推出的图片需要重新到网络进行加载
[_ramImg setObject:iconImg forKey:model.icon];
//让操作只刷新操作创建时的行数据,解决网络延迟问题2
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation: UITableViewRowAnimationNone];
//8.数据写入到沙盒
[data writeToFile:[model.icon cachesPath] atomically:YES];
}
//9.操作完成,将该操作从缓存池里移除,此时无论图片网络请求是否成功都需要移除,所以要放在判断后面,如果不移除,没有请求成功的图片就无法再进入操作里重新请求数据
[_opList removeObjectForKey:model.icon];
}];
}];
//5.将操作加入操作缓存池,对为完成的操作进行记录
[_opList setObject:op forKey:model.icon];
[_queue addOperation:op];
return cell;
}
//解决内存警告,释放内存缓存
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
[_ramImg removeAllObjects];
[_opList removeAllObjects];
}