1.NSOperation–最大并发数

什么是并发数
同时执行的任务数,比如同时开3个线程执行3个任务,并发数就是3

最大并发数的相关方法

-(NSInteger)maxConcurrentOperationCount;
 -(void)setMaxConcurrentOperationCount:(NSInteger)cnt;

执行的过程
1.把操作添加到队列self.queue addOperationWithBlock
2.去线程池取空闲的线程,如果没有就创建线程
3.把操作交给从线程池中取出的线程执行
4.执行完成后,把线程再放回线程池中
5.重复2、3、4直到所有的操作都执行

#import "ViewController.h"

@interface ViewController ()
@property(nonatomic,strong)NSOperationQueue* queue;
@end

@implementation ViewController
//懒加载
-(NSOperationQueue*)queue{
  if(_queue==nil){
    _queue=[[NSOperationQueue alloc]init];
    //设置最大并发数
    _queue.maxConcurrentOperationCount=2;
  }
  return _queue;
}


- (void)viewDidLoad {
  [super viewDidLoad];
  
  for (int i=0; i<20; i++) {
    [self.queue addOperationWithBlock:^{
      [NSThread sleepForTimeInterval:1];
      NSLog(@"%d %@",i,[NSThread currentThread]);
    }];
  }
}
@end
2020-08-07 10:44:10.166405+0800 IOS113[7379:88246] 0 <NSThread: 0x6000034ecd40>{number = 4, name = (null)}
 2020-08-07 10:44:10.166403+0800 IOS113[7379:88244] 1 <NSThread: 0x6000034ecac0>{number = 5, name = (null)}
 2020-08-07 10:44:11.170643+0800 IOS113[7379:88244] 3 <NSThread: 0x6000034ecac0>{number = 5, name = (null)}
 2020-08-07 10:44:11.170646+0800 IOS113[7379:88247] 2 <NSThread: 0x6000034efb80>{number = 7, name = (null)}


以上开了4,5,7三个线程,比设定的值要多1,这是oc相应机制引起的,不是错误

2.队列的暂停,取消,恢复

取消队列的所有操作
-(void)cancelAllOperation
也可以调用NSOperation的-(void)cancel方法取消单个操作
暂停和恢复队列
-(void)setSuspended:(BOOl)b;//YES代表暂停队列,NO表示恢复队列
-(BOOL)isSuspended;

#import "ViewController.h"

@interface ViewController ()
@property(nonatomic,strong)NSOperationQueue* queue;

- (IBAction)cancelQueue:(id)sender;
- (IBAction)pauseQueue:(id)sender;
- (IBAction)continueQueue:(id)sender;


@end

@implementation ViewController
//懒加载

-(NSOperationQueue*)queue{
  if(_queue==nil){
    _queue=[[NSOperationQueue alloc]init];
    //设置最大并发数
    _queue.maxConcurrentOperationCount=2;
  }
  return _queue;
}


- (void)viewDidLoad {
  [super viewDidLoad];
  
  for (int i=0; i<20; i++) {
    [self.queue addOperationWithBlock:^{
      [NSThread sleepForTimeInterval:1];
      NSLog(@"%d %@",i,[NSThread currentThread]);
    }];
  }
}

//继续队列操作

- (IBAction)continueQueue:(id)sender {
  [self.queue setSuspended:NO];
  NSLog(@"继续");
}
//暂停队列操作

- (IBAction)pauseQueue:(id)sender {
  [self.queue setSuspended:YES];
   NSLog(@"暂停");//暂停后续操作,因此还会继续执行一会
}
//取消队列操作
- (IBAction)cancelQueue:(id)sender {
  [self.queue cancelAllOperations];
   NSLog(@"取消");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  //队列中的操作数
  
  NSLog(@"%zd",self.queue.operationCount);
}
@end
3.摇奖机案例
#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *first;
@property (weak, nonatomic) IBOutlet UILabel *second;
@property (weak, nonatomic) IBOutlet UILabel *third;
@property (weak, nonatomic) IBOutlet UIButton *starButton;

//由于随机数死循环,界面被卡死,所有创建一个全局队列
@property(nonatomic,strong)NSOperationQueue* queue;

- (IBAction)start:(UIButton*)sender;

@end

@implementation ViewController
//懒加载数据
//重写queue队列
-(NSOperationQueue*)queue{
  if (!_queue) {
    //如果没有队列则创建一个队列
    _queue=[[NSOperationQueue alloc]init];
  }
  return _queue;
}
- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view.
}

//点击开始按钮
- (IBAction)start:(UIButton*)sender {
  //当队列中有操作时,不添加操作
  if (self.queue.operationCount==0) {
    //队列中加入一个任务,异步执行
    [self.queue addOperationWithBlock:^{
      [self randomNumber];
    }];
    
    [self.starButton setTitle:@"暂停" forState:UIControlStateNormal];
    [self.queue setSuspended:NO];

  }else if(!self.queue.isSuspended){
    //正在执行的时候
    //先把当前的操作执行完毕,暂停后续操作
    [self.queue setSuspended:YES];
    [self.starButton setTitle:@"开始" forState:UIControlStateNormal];
  }
  
  
}

//随机生成3个数字,显示到label上
-(void)randomNumber{
  while (!self.queue.isSuspended) {
    [NSThread sleepForTimeInterval:0.05];
    //生成随机数
    int num1=arc4random_uniform(10);
    int num2=arc4random_uniform(10);
    int num3=arc4random_uniform(10);
    
    //回到主线程上更新UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
      //给label赋值
      self.first.text=[NSString stringWithFormat:@"%d",num1 ];
      self.second.text=[NSString stringWithFormat:@"%d",num2 ];
      self.third.text=[NSString stringWithFormat:@"%d",num3 ];
    }];
    
    
  }
}
@end

iOS开发多线程的 实际使用场景 ios 多线程并发_#import

4.操作的优先级

设置NSOperation在queue中的优先级,可以改变操作执行的执行优先级

-(nsoperationqueuePriority)queuePriority;
 -(void)setQuepriority:(nsopertationqueuePriority)p;

IOS8以后推荐使用服务质量qualityOfService
优先级高意味着有更高几率去执行,但不是高优先级执行完再执行低优先级,交叉执行,高优先级获得更高机会(一般比低优先级提前执行完)

#import "ViewController.h"

@interface ViewController ()
//创建全局队列
@property(nonatomic,strong)NSOperationQueue* queue;

@end

@implementation ViewController
//懒加载数据
//重写全局属性queue
-(NSOperationQueue*)queue{
  if (!_queue) {
    _queue=[[NSOperationQueue alloc]init];
  }
  return _queue;
}
- (void)viewDidLoad {
  [super viewDidLoad];
  //操作1
  NSBlockOperation* op1=[NSBlockOperation blockOperationWithBlock:^{
    for (int i=0; i<10; i++) {
      NSLog(@"op1 %d",i);
    }
  }];
  
  //设置优先级最高
  op1.qualityOfService=NSQualityOfServiceUserInteractive;
  [self.queue addOperation:op1];
  
  //操作2
  NSBlockOperation* op2=[NSBlockOperation blockOperationWithBlock:^{
    for (int i=0; i<10; i++) {
      NSLog(@"op2 %d",i);
    }
  }];
  //设置优先级最低
  op1.qualityOfService=NSQualityOfServiceBackground;
  [self.queue addOperation:op2];
}


@end

打印

2020-08-07 15:18:20.288793+0800 IOS115[8401:190730] op1 0
 2020-08-07 15:18:20.288793+0800 IOS115[8401:190728] op2 0
 2020-08-07 15:18:20.288880+0800 IOS115[8401:190730] op1 1
 2020-08-07 15:18:20.288880+0800 IOS115[8401:190728] op2 1
 2020-08-07 15:18:20.288933+0800 IOS115[8401:190730] op1 2
 2020-08-07 15:18:20.288955+0800 IOS115[8401:190728] op2 2
 2020-08-07 15:18:20.289002+0800 IOS115[8401:190730] op1 3
 2020-08-07 15:18:20.289014+0800 IOS115[8401:190728] op2 3
 2020-08-07 15:18:20.289056+0800 IOS115[8401:190730] op1 4
 2020-08-07 15:18:20.289083+0800 IOS115[8401:190728] op2 4
 2020-08-07 15:18:20.289213+0800 IOS115[8401:190730] op1 5
 2020-08-07 15:18:20.289324+0800 IOS115[8401:190730] op1 6
 2020-08-07 15:18:20.289424+0800 IOS115[8401:190730] op1 7
 2020-08-07 15:18:20.289519+0800 IOS115[8401:190730] op1 8
 2020-08-07 15:18:20.289622+0800 IOS115[8401:190730] op1 9
 2020-08-07 15:18:20.289819+0800 IOS115[8401:190728] op2 5
 2020-08-07 15:18:20.289950+0800 IOS115[8401:190728] op2 6
 2020-08-07 15:18:20.290179+0800 IOS115[8401:190728] op2 7
 2020-08-07 15:18:20.290296+0800 IOS115[8401:190728] op2 8
 2020-08-07 15:18:20.290513+0800 IOS115[8401:190728] op2 9
5.线程的监听
#import "ViewController.h"

@interface ViewController ()
//创建全局队列
@property(nonatomic,strong)NSOperationQueue* queue;

@end

@implementation ViewController
//懒加载数据
//重写全局属性queue
-(NSOperationQueue*)queue{
  if (!_queue) {
    _queue=[[NSOperationQueue alloc]init];
  }
  return _queue;
}
- (void)viewDidLoad {
  [super viewDidLoad];
  //操作1
  NSBlockOperation* op1=[NSBlockOperation blockOperationWithBlock:^{
    for (int i=0; i<10; i++) {
      NSLog(@"op1 %d",i);
    }
  }];
  
  //设置优先级最高
  op1.qualityOfService=NSQualityOfServiceUserInteractive;
  [self.queue addOperation:op1];
  //监听操作执行完毕后执行的代码,执行在子线程上
  [op1 setCompletionBlock:^{
    NSLog(@"***op1操作已经完成****,%@",[NSThread currentThread]);
  }];
  
  //操作2
  NSBlockOperation* op2=[NSBlockOperation blockOperationWithBlock:^{
    for (int i=0; i<10; i++) {
      NSLog(@"op2 %d",i);
    }
  }];
  //设置优先级最低
  op1.qualityOfService=NSQualityOfServiceBackground;
  [self.queue addOperation:op2];
}


@end

打印

2020-08-07 15:24:38.617555+0800 IOS115[8445:194881] op1 0
 2020-08-07 15:24:38.617554+0800 IOS115[8445:194884] op2 0
 2020-08-07 15:24:38.617632+0800 IOS115[8445:194884] op2 1
 2020-08-07 15:24:38.617633+0800 IOS115[8445:194881] op1 1
 2020-08-07 15:24:38.617690+0800 IOS115[8445:194884] op2 2
 2020-08-07 15:24:38.617708+0800 IOS115[8445:194881] op1 2
 2020-08-07 15:24:38.617739+0800 IOS115[8445:194884] op2 3
 2020-08-07 15:24:38.617768+0800 IOS115[8445:194881] op1 3
 2020-08-07 15:24:38.617798+0800 IOS115[8445:194884] op2 4
 2020-08-07 15:24:38.617826+0800 IOS115[8445:194881] op1 4
 2020-08-07 15:24:38.617970+0800 IOS115[8445:194881] op1 5
 2020-08-07 15:24:38.618083+0800 IOS115[8445:194881] op1 6
 2020-08-07 15:24:38.618186+0800 IOS115[8445:194881] op1 7
 2020-08-07 15:24:38.618286+0800 IOS115[8445:194881] op1 8
 2020-08-07 15:24:38.618388+0800 IOS115[8445:194881] op1 9
 2020-08-07 15:24:38.618560+0800 IOS115[8445:194882] op1操作已经完成*,<NSThread: 0x600001c84380>{number = 5, name = (null)}
 2020-08-07 15:24:38.618582+0800 IOS115[8445:194884] op2 5
 2020-08-07 15:24:38.618936+0800 IOS115[8445:194884] op2 6
 2020-08-07 15:24:38.619104+0800 IOS115[8445:194884] op2 7
 2020-08-07 15:24:38.619363+0800 IOS115[8445:194884] op2 8
 2020-08-07 15:24:38.619627+0800 IOS115[8445:194884] op2 9
6.操作依赖

NSOperation之间可以设置依赖来保证执行顺序
比如一定要让操作A执行完成后,才能执行操作B,可以这么写
[operationB addDependency:operationA];操作B依赖于操作A
可以在不同queue的NSOperation之间创建依赖关系

注意要避免相互依赖

iOS开发多线程的 实际使用场景 ios 多线程并发_#import_02

#import "ViewController.h"

@interface ViewController ()
//创建全局队列属性
@property(nonatomic,strong)NSOperationQueue* queue;

@end

@implementation ViewController
//懒加载
//重写queue属性
-(NSOperationQueue*)queue{
  if (!_queue) {
    _queue=[[NSOperationQueue alloc]init];
  }
  return _queue;
}
- (void)viewDidLoad {
  [super viewDidLoad];
  //下载
  NSBlockOperation* op1=[NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下载");
  }];
  
  //解压
  NSBlockOperation* op2=[NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"解压");
  }];
  
  
  //升级完成
  NSBlockOperation* op3=[NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"升级完成");
  }];
  //由于线程之间是无序的所以要设置操作依赖
  [op2 addDependency:op1];
  [op3 addDependency:op2];

  //将操作添加到队列中
  [self.queue addOperations:@[op1,op2] waitUntilFinished:NO];
  //不在一个队列中依然可以实现依赖,依赖关系可以跨队列执行
  [[NSOperationQueue mainQueue]addOperation:op3];
}


@end

打印

2020-08-07 15:55:39.104901+0800 IOS116[8658:211273] 下载
 2020-08-07 15:55:39.105011+0800 IOS116[8658:211273] 解压
 2020-08-07 15:55:39.113781+0800 IOS116[8658:211123] 升级完成
7.UITableView中显示图片

要求:
1异步下载图片
2不能重复发起相同的下载请求
3要把图片缓存到内存和文件中
4图片显示不能错位

1.对info.plist文件添加如下代码,用于允许访问http连接

NSAppTransportSecurity


NSAllowsArbitraryLoads



iOS开发多线程的 实际使用场景 ios 多线程并发_#import_03


2.plist文件结构

iOS开发多线程的 实际使用场景 ios 多线程并发_iOS开发多线程的 实际使用场景_04


3.创建数据模型FRAppInfo类,并把懒加载主要逻辑写在里面

FRAppInfo.h

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FRAppInfo : NSObject
@property(nonatomic,copy)NSString* name;
@property(nonatomic,copy)NSString* icon;
@property(nonatomic,copy)NSString* download;

//定义一个模型属性,来存放缓存图片
//@property(nonatomic,strong)UIImage* image;

+(instancetype)appInfoWithDictionary:(NSDictionary*)dict;

//获取所有的模型数据
+(NSArray *)appInfos;
@end

NS_ASSUME_NONNULL_END

FRAppInfo.m

#import "FRAppInfo.h"

@implementation FRAppInfo
+(instancetype)appInfoWithDictionary:(NSDictionary*)dict{
  FRAppInfo* appInfo=[[self alloc]init];
  //用kvc方式给属性赋值
  [appInfo setValuesForKeysWithDictionary:dict];
  return appInfo;
}

//获取所有的模型数据
+(NSArray *)appInfos{
 
  //加载plist
  //创建plist文件路径
  NSString* path=[[NSBundle mainBundle]pathForResource:@"apps.plist" ofType:nil];
  NSArray* appInfos=[NSArray arrayWithContentsOfFile:path];

  NSMutableArray* models=[NSMutableArray arrayWithCapacity:10];//先开辟是个元素的内存空间
  //遍历数组的另一种方式
  [appInfos enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    //字典转模型
    FRAppInfo* model=[self appInfoWithDictionary:obj];//这里的obj就是从字典文件里读取出来的字典对象
    [models addObject:model];
    
  }];

  return models.copy;//对可变数组进行copy操作,变可变数组为不可变数组
}

4.创建自定义cell类FRAppInfoCell类
FRAppInfoCell.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface FRAppInfoCell : UITableViewCell

@end

NS_ASSUME_NONNULL_END

FRAppInfoCell.m

#import "FRAppInfoCell.h"

@implementation FRAppInfoCell

//重写layoutSubviews方法
-(void)layoutSubviews{
  [super layoutSubviews];
}

@end

5.创建UITableViewController,并使用ViewController类来指向他

#import "ViewController.h"
#import "FRAppInfo.h"
#import "FRAppInfoCell.h"
@interface ViewController ()
//创建一个模型属性
@property(nonatomic,strong)NSArray* appInfos;
//创建一个全局队列
@property(nonatomic,strong)NSOperationQueue* queue;
//创建一个图片的缓存池
@property(nonatomic,strong)NSMutableDictionary* imageCache;
//创建一个下载操作缓存池
@property(nonatomic,strong)NSMutableDictionary* downloadCache;



@end

@implementation ViewController
#define mark ---懒加载数据方法-----
//重写appInfo模型
-(NSArray*)appInfos{
  if (!_appInfos) {
    _appInfos=[FRAppInfo appInfos];
  }
  return _appInfos;
}

- (void)viewDidLoad {
  [super viewDidLoad];
  //1测试模型数据
  NSLog(@"%@",self.appInfos);

}

//重写全局队列属性
-(NSOperationQueue*)queue{
  if (!_queue) {
    _queue=[[NSOperationQueue alloc]init];
  }
  return _queue;
}
//重写可变缓冲池属性
-(NSMutableDictionary*)imageCache{
  if (!_imageCache) {
    _imageCache=[NSMutableDictionary dictionaryWithCapacity:10];
  }
  return _imageCache;
}
//重写可变下载操作缓冲池属性

-(NSMutableDictionary*)downloadCache{
  if (!_downloadCache) {
    _downloadCache=[NSMutableDictionary dictionaryWithCapacity:10];
  }
  return _downloadCache;
}

#define mark ----数据源方法-----
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
  return self.appInfos.count;
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
  //创建可重用的cell
  static NSString* reuseID=@"appInfoId";
  FRAppInfoCell* cell=[tableView dequeueReusableCellWithIdentifier:reuseID];
  if (cell==nil) {
    cell=[[FRAppInfoCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseID];
  }
  
  //获取数据,获取cell内部子控件赋值
  FRAppInfo* appInfo=self.appInfos[indexPath.row];
  //cell内部的子控件都是懒加载的
  //当返回cell之前,会调用cell的layoutSubviews方法
  cell.textLabel.text=appInfo.name;
  cell.detailTextLabel.text=appInfo.download;
  
  //判断有没有图片缓存
  if (self.imageCache[appInfo.icon]) {
    NSLog(@"这是从缓存中加载的图片");
    cell.imageView.image=self.imageCache[appInfo.icon];
    return cell;
  }
  //设置占位图片
  cell.imageView.image=[UIImage imageNamed:@"user_default"];
  
  //判断下载操作缓存池中是否有对应的操作
   if (self.downloadCache[appInfo.icon]) {
     NSLog(@"这是从操作缓存中加载的操作,正在拼命下载操作");
     return cell;
   }
  
  //下载并设置图片(异步)
  //模拟网速较慢,主界面上有耗时操作,需要改造成异步下载
  NSBlockOperation* op=[NSBlockOperation blockOperationWithBlock:^{
    
    //[NSThread sleepForTimeInterval:0.5];//模拟0.5秒网络延时。
    //模拟图片下载速度慢
    if (indexPath.row>11) {
      [NSThread sleepForTimeInterval:5];
    }
    
    //下载网络中的图片
    NSLog(@"下载网络图片");
    //下载图片
    NSURL *url=[NSURL URLWithString:appInfo.icon];
    NSData* data=[NSData dataWithContentsOfURL:url];//二进制数据
    UIImage* image=[UIImage imageWithData:data];
    
    
    
    //在主线程上更新ui
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
      //缓存图片(用内存空间换缓存时间)
      self.imageCache[appInfo.icon]=image;
      
      //移除下载操作缓存池
      [self.downloadCache removeObjectForKey:appInfo.icon];
      //cell.imageView.image=image;
      //解决图片显示错行的问题
      //全部cell对部分行(indexPath)加载,有于有缓存图片,速度会很快
      [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
    }];
  }];
  //把操作添加到op当中
  [self.queue addOperation:op];
  //把操作添加到下载操作缓存池当中
  self.downloadCache[appInfo.icon]=op;
  //返回cell
  return cell;
}

//接收到内存警告时
-(void)didReceiveMemoryWarning{
  //清理内存
  [self.imageCache removeAllObjects];
  [self.downloadCache removeAllObjects];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
  //点击cell的时候,输出当前队列的操作数
  NSLog(@"队列的操作数:%zd",self.queue.operationCount);
}
@end

效果

iOS开发多线程的 实际使用场景 ios 多线程并发_i++_05

8.大总结
//访问网络
 //NSAppTransportSecurity
 //
 //NSAllowsArbitraryLoads
 //
 ////1 GCD和NSOperation的区别
//2 最大并发数
//3 暂停 继续 取消
 //暂停和取消 暂停和取消的是没有执行的操作,会执行完当前操作//4 摇奖机
//5 服务质量类似于多线程的优先级
//6 completionBlock 当操作执行完成之后 调用的block (回调)
//7 操作依赖 避免循环依赖
//------------------------
 //8 异步下载网络图片//0 分析
 //1 创建模型类,获取数据,测试
 //2 数据源方法
 //3 同步下载图片–如果网速比较慢,界面会卡顿
 //4 异步下载图片–图片显示不出来,点击cell或者上下拖动图片可以显示
 //解决,使用占位图片
 //5 图片缓存–把网络上下载的图片,保存到内存–图片存储在模型对象中
 //解决,图片重复下载,把图片缓存到内存中,节省用户的流量 (拿空间换取执行时间)
 //6 解决图片下载速度特别慢,出现的错行问题。
 //当图片下载完成之后,重新加载对应的cell
 //7 当收到内存警告,要清理内存,如果图片存储在模型对象中,不好清理内存
 //图片的缓存池//8 当有些图片下载速度比较慢,上下不停滚动的时候,会重复下载图片,会浪费流量
 //判断当前是否有对应图片的下载操作,如果没有添加下载操作,如果有不要重复创建操作
 //下载操作的缓存池//9 分析是否有循环引用的问题
 //vc -> queue 和 downloadCache -> op -> block -> self(vc)//10 如果没有联网的话,返回cell的方法会不停执行。
 //判断下载的图片是否为空//11 沙盒缓存