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
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之间创建依赖关系
注意要避免相互依赖
#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
2.plist文件结构
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
效果
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 沙盒缓存