多线程3
2016-10-20 本文已影响0人
project_DLQ
同步任务的作用
//需求:登录- 付费 - 下载 - 通知用户
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self CGDDemo];
}
/*
1.同步任务只在当前线程执行
2.同步任务之间是有执行的,前面一个同步任务不执行完,后面的同步任务无法执行
*/
-(void)CGDDemo{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//创在一个子线程的环境,让多个同步任务在子线程里面按顺序执行
dispatch_async(queue, ^{
//登录
dispatch_sync(queue, ^{
NSLog(@"登录:%@",[NSThread currentThread]);
});
//付费
dispatch_sync(queue, ^{
NSLog(@"付费:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"下载:%@",[NSThread currentThread]);
});
//通知用户
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"通知用户:%@",[NSThread currentThread]);
});
});
}
GCD阻塞(Barrier)
使用场景:
主要用于在多个异步操作完成之后,统一对非线程安全的对象进行操作
适合大规模数据的I/O(读写)操作
@interface ViewController ()
//图片数组
@property (strong,nonatomic) NSMutableArray *imagesM;
@end
//需求:异步下载多张图片,下载完成之后,把图片对象保存到数组
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//实例化数组:为什么要实例化?因为要向里面添加对象,需要一定的内存空间,不实例化就没有内存空间
self.imagesM = [NSMutableArray array];
[self downloadImages2];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"%tu",self.imagesM.count);
}
//使用GCD阻塞:dispatch_barrier_async
-(void)downloadImages2{
//使用GCD的barrier实现,必须使用自定义的并发队列
//dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//定义局部的队列
dispatch_queue_t queue = dispatch_queue_create("hehe", DISPATCH_QUEUE_CONCURRENT);
//循环的创建多个异步任务,同时下载多张图片
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
//1.获取图片名字
NSString *name = [NSString stringWithFormat:@"%02zd.jpg",(i%10+1)];
//2.获取本地图片的本地URL
NSURL *url = [[NSBundle mainBundle]URLForResource:name withExtension:nil];
//3.获取image对象
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
//模拟网络延迟
// [NSThread sleepForTimeInterval:0.5];
NSLog(@"%@ - - %@",name,[NSThread currentThread]);
//图片下载完成之后,需要保存到可变数组
//有可能出现多尔衮线程同时向同一个角标插入图片对象
//queue:只有当queue里面的任务都执行完,再执行barrier对应的任务
//所以不能使用全局队列:因为全局对象是共享给整个APP使用的,只有一个,有可能其他界面用到了
//barrier会在一个线程里面吧之前没有执行的代码,再执行一遍,会单开一个线程单独的操作非安全的类
dispatch_barrier_async(queue, ^{
NSLog(@"%@ - - %@",name,[NSThread currentThread]);
[self.imagesM addObject:image];
});
});
}
}
//错误演示
-(void)downloadImages{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//循环的创建多个异步任务,同时下载多张图片
for (int i = 0; i < 100000; i++) {
dispatch_async(queue, ^{
//1.获取图片名字
NSString *name = [NSString stringWithFormat:@"%02zd.jpg",(i%10+1)];
//2.获取本地图片的本地URL
NSURL *url = [[NSBundle mainBundle]URLForResource:name withExtension:nil];
//3.获取image对象
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
//模拟网络延迟
// [NSThread sleepForTimeInterval:0.5];
NSLog(@"%@ - - %@",name,[NSThread currentThread]);
//图片下载完成之后,需要保存到可变数组
//有可能出现多尔衮线程同时向同一个角标插入图片对象
[self.imagesM addObject:image];
});
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
GCD延迟操作
-(void)after2{
NSLog(@"开始");
//参数1:延迟时间:NSEC_PER_SEC 10亿纳秒(非常精确,比NSTimer要精确)
dispatch_time_t when =dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
//参数2:延迟任务执行的队列
dispatch_queue_t queue = dispatch_get_main_queue();
//参数3:延迟的任务
dispatch_block_t block = ^{
NSLog(@"哈哈 %@",[NSThread currentThread]);
};
//延迟默认是异步执行的
//延迟多长时间,在哪个队列调度执行哪个异步任务
//延迟默认是异步执行的
dispatch_after(when, queue, block);
NSLog(@"结束");
}
-(void)after{
NSLog(@"开始");
//延迟默认是异步执行的
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"哈哈 %@",[NSThread currentThread]);
});
NSLog(@"结束");
}
GCD一次性执行
dispatch_once_t 内部有一把锁,是能够保证 线程安全,而却是苹果公司推荐使用的
在开发中,有些代码指向就只执行一次
//验证一次性执行是否安全
-(void)onceDemo2{
for (int i = 0; i < 1000; i++) {
NSLog(@"gfdsdfghs");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"鸟哥");
});
});
}
}
//验证一次性执行的可靠性
-(void)onceDemo1{
NSLog(@"start");
//一次性执行就是准备好的代码块
static dispatch_once_t onceToken;//静态区 onceToken的地址不会发生变化,是个long类型的值
//原理:onceToken被保存在静态存储区,地址唯一,有个初始值是0;当第一次执行时,会检测初始值是否为0,如果是0就执行代码块,反之,就不执行代码块
//提示:当第一次执行完完之后,会修改onceToken的值为非0的值
NSLog(@"%ld",onceToken);
dispatch_once(&onceToken, ^{
//只被执行一次的代码
NSLog(@"鸟哥");
});
}
单例设计模式
/*
once的使用场景:单例模式的设计
单例模式
1.对象有且只有一个
2.保存在静态存储区
3.声明周期和APP一样长
单例的使用场景
1.在APP中,必须有且只有一个对象的类,要设计成单例模式(音乐播放器...)
2.在APP开发中,有些类,会经常频繁的使用,比如一些工具类(网络请求工具类,数据路管理工具类)
单例的缺点
1.只要创建了单例对象,就会一直占用内存,直到这个程序退出,单例对象的内存空间才释放
2.单例不能过多的使用,滥用
系统单例
1.NSFileManager/NSUserDefault/ NSNotifacationCenter....
单例分两种:恶汉式和懒汉式,实际开发中使用懒汉式就够了,懒汉式仅做了解
面试时:有可能手写单例
单例是一种是设计模式,类似于MVC..设计模式
开发中不需要重写跟开辟内存空间和实例化相关的任何方法
*/
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NetWorkTool *tool0 = [[NetWorkTool alloc] init];
NetWorkTool *tool1 = [NetWorkTool shardTool];
NetWorkTool *tool2 = [NetWorkTool shardTool];
NSLog(@"%@ -- %@--%@",tool0,tool1,tool2);
FMDBManager *manager0 = [[FMDBManager alloc] init];
FMDBManager *manager1 = [FMDBManager sharedManager];
FMDBManager *manager2 = [FMDBManager sharedManager];
NSLog(@"%@ -- %@--%@",manager0,manager1,manager2);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
懒汉式:
@interface NetWorkTool : NSObject
//单例的全局访问点,返回这个单例对象
//懒汉式单例:尽量创建的晚
+(instancetype)shardTool;
@end
@implementation NetWorkTool
+(instancetype)shardTool{
//定义静态对象
static NetWorkTool *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc]init];;
});
return instance;
}
@end
饿汉式:
@interface FMDBManager : NSObject
//全局访问点
//饿汉式
+(instancetype)sharedManager;
@end
@implementation FMDBManager
static id instance;
//当某个类第一次使用时就会调用一次
//线程安全的
+(void)initialize{
instance = [[self alloc] init];
}
+(instancetype)sharedManager{
return instance;
}
@end
GCD的调度组(Group)
实际开发中,有可能会常用
新浪微博中就会用到
group是监听一组异步任务是否执行结束,如果执行结束,就可以得到统一的通知
/*
void
dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_retain(group);
dispatch_group_enter(group);
dispatch_async(queue, ^{
block();
dispatch_group_leave(group);
dispatch_release(group);
});
}
*/
/*
1.当enter < leave会崩溃
2.当enter > leave会检测不到
3.enter和leave必须成对出现
*/
-(void)groupDemo2{
//组
dispatch_group_t group = dispatch_group_create();
//队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//异步下载图片A
//向group里面添加标记
dispatch_group_enter(group);
//异步执行下载任务
dispatch_async(queue, ^{
//执行任务
NSLog(@"假装下载图片A:%@",[NSThread currentThread]);
//任务执行完之后,把标记从组里面移除
dispatch_group_leave(group);
});
//下载图片B
//向group里面添加标记
dispatch_group_enter(group);
//异步执行下载任务
dispatch_async(queue, ^{
//执行任务
NSLog(@"假装下载图片B:%@",[NSThread currentThread]);
//任务执行完之后,把标记从组里面移除
dispatch_group_leave(group);
});
//下载图片C
//向group里面添加标记
dispatch_group_enter(group);
//异步执行下载任务
dispatch_async(queue, ^{
//执行任务
NSLog(@"假装下载图片C:%@",[NSThread currentThread]);
//任务执行完之后,把标记从组里面移除
dispatch_group_leave(group);
});
//监听
dispatch_group_notify(group, queue, ^{
NSLog(@"图片下载完了么?--%@",[NSThread currentThread]);
});
}
//group监听一组异步任务是否执行结束
-(void)groupDemo{
//组:
dispatch_group_t group = dispatch_group_create();
//队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//这个异步函数,是把异步任务添加到队列,并且向"组"(group)里面添加了一个标记
dispatch_group_async(group, queue, ^{
NSLog(@"假装下载图片A:%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"假装下载图片B:%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"假装下载图片C:%@",[NSThread currentThread]);
});
//监听group里面的任务是否执行完:一般检测到下载结束之后,在这里面刷新UI的操作
//dispatch_group_notify 是在子线程执行的,需要回到主线程刷新UI
//dispatch_group_notify:一旦检测到group里面的标记没有了,就开始调用dispatch_group_notify里面的方法
dispatch_group_notify(group, queue, ^{
NSLog(@"图片下载完了么?--%@",[NSThread currentThread]);
});
}
NSOperation
NSOperation简介
- operation:操作的意思
- 是oc语言中 基于GCD的面向对象的操作
- 使用起来比GCD更加简单(面向对象)
- 提供一些用GCD不好实现的功能
/*
NSOperation
1.它是一个抽象类,无法直接使用;因为只有定义,无实现;实现交给子类;他的作用就是作为父类,约束子类"共有"的属性和方法
2.子类
NSInvocationOperation
NSBlockOperation
3.队列
NSOperationQueue
4.GCD的核心概念:将任务添加到队列
5:OP的核心概念:将操作添加到队列
6:op的使用步骤
创建队列
创建操作
将操作添加到队列
*/
NSInvocationOperation
/*
NSInvocationOperation:非常古老,ios2.0,开发中不用
*/
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self opDemo3];
}
-(void)opDemo3{
for (int i= 0; i< 10; i++) {
//创建队列:默认并发
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//创建操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo:) object:@(i)];
//将操作添加到对象
[queue addOperation:op];
}
}
-(void)opDemo2{
//创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//创建操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo:) object:@"invocationOp"];
//把操作添加到对象
[queue addOperation:op];
}
-(void)opDemo1{
//创建操作对象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo:) object:@"invocation"];
//调用start会在当前线程执行
[op start];
}
-(void)demo:(id)param{
NSLog(@"%@--%@",param,[NSThread currentThread]);
}
NSBlockOperation
@interface ViewController ()
//定义全局队列
@property (strong,nonatomic) NSOperationQueue *queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//实例化全局队列
self.queue = [[NSOperationQueue alloc] init];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self OPDemo5];
}
#pragma mark - 线程间通信
-(void)OPDemo5{
[self.queue addOperationWithBlock:^{
NSLog(@"模拟下载..%@",[NSThread currentThread]);
//如果异步任务执行后,拿到结果,就通知主线程刷新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"模拟刷新ui..%@",[NSThread currentThread]);
}];
}];
}
-(void)OPDemo4{
//直接把操作添加到队列:无法拿到操作对象
[self.queue addOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
}
-(void)OPDemo3{
//创建队列:默认并发
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 10;i++) {
//创建操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%zd - %@",i,[NSThread currentThread]);
}];
//把操作添加到队列
[queue addOperation:op];
}
}
-(void)OPDemo2{
//创建队列:默认并发
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//创建操作 默认异步
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
//将异步操作添加到并发队列
[queue addOperation:op];
}
-(void)OPDemo1{
//创建对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
//调用start方法:依然在当前主线程执行
[op start];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
NSOperation和GCD对比
GCD
- 核心概念:将任务添加到队列,并且指定任务执行的函数
- CGD是C语言的API
- ios 4.0推出的,针对多核处理器的并发技术
- 任务是封装在block中
- 要停止已经加入到队列的任务需要写复杂的代码
- 只能设置队列的优先级
- 建立任务间依赖关系比较复杂
- 高级功能
阻塞barrier
一次性once
延迟操作after
调度组group
NSOperation
- 核心思想: 把操作添加到队列
- OC框架,更加面向对象,是对GCD的封装
- ios2.0退出,苹果退出GCD之后,对NSOperation的底层全部重写
- 任务封装在operation对象中的,为我们提供了更多的选择,操作对象更加方便
- 可以取消掉队列中的任务,正在执行的任务除外
- 可以设置队列中的每一个操作的优先级
- 可以跨队列设置操作的依赖关系
- 高级功能
最大操作并发数(GCD不好做)
继续/暂停/全部取消
跨队列设置操作的依赖关系
操作服务质量与监听操作执行结束
@interface ViewController ()
@property (nonatomic,strong) NSOperationQueue *queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.queue = [[NSOperationQueue alloc] init];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self opDemo];
}
-(void)opDemo{
//操作1
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i =0; i < 5; i++) {
NSLog(@"op1 %zd-%@",i,[NSThread currentThread]);
}
}];
//设置op1的服务质量最高:服务质量等价于优先级,决定了队列里面的某些操作有更多的机会会被调用
op1.qualityOfService = NSQualityOfServiceUserInteractive;
//监听某个操作是否执行结束:一旦操作执行完毕,底层会自动回调completion
void(^completionBlock)() = ^{
NSLog(@"op1执行完了吗?%@",[NSThread currentThread]);
};
// //建议使用:更加简洁;今后如果给block的属性赋值,建议使用setter方法,block会自动补全
// [op1 setCompletionBlock:^{
//
// }];
op1.completionBlock = completionBlock;
//把操作添加到队列
[self.queue addOperation:op1];
//操作2
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i =0; i < 5; i++) {
NSLog(@"op2 %zd-%@",i,[NSThread currentThread]);
}
}];
//op2的服务质量最低
op2.qualityOfService = NSQualityOfServiceBackground;
//把操作添加到队列
[self.queue addOperation:op2];
NSLog(@"------");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
最大并发数
@interface ViewController ()
//队列
@property (nonatomic,strong) NSOperationQueue *queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//实例化队列
self.queue = [[NSOperationQueue alloc] init];
//设置队列最大并发数
self.queue.maxConcurrentOperationCount = 2;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self opDemo];
}
-(void)opDemo{
for (int i = 0; i < 20; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
//模拟网络延迟:仅仅是为了放大队列最大并发数的执行的效果
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%zd - %@",i,[NSThread currentThread]);
}];
[self.queue addOperation:op];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
队列的暂停继续和取消
@interface ViewController ()
//队列
@property (nonatomic,strong) NSOperationQueue *queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//实例化队列
self.queue = [[NSOperationQueue alloc] init];
//设置队列最大并发数
self.queue.maxConcurrentOperationCount = 2;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self opDemo];
}
/*
正在执行的操作无法取消
如果你非要取消正在执行的操作,需要自定义NSOperation
取消全部操作会有一定的时间延迟,具体延迟多久,是系统自己算的
为什么会有延迟?
队列一旦调用了cancelAllOperations,队列会遍历自己里面的所有操作
每遍历出一个操作,就会调用cancle方法
一旦操作对象调用了cancle方法,那么操作对象的canclled的属性就会被设置成YES,表示该操作不能正常执行
一旦队列发现,操作对象的cancelled属性为YES了就不会调度其执行,会移除掉,operationCount不会对其记录
*/
#pragma mark - 取消
-(IBAction)cancelALL:(id)sender{
//取消 /移除队列里面所有的操作
[self.queue cancelAllOperations];
NSLog(@"取消全部 %tu",self.queue.operationCount);
}
#pragma mark - 继续
-(IBAction)jixu:(id)sender{
//使队列继续调度任务
self.queue.suspended = NO;
NSLog(@"继续 %tu",self.queue.operationCount);
}
#pragma mark - 暂停
/*
正在执行的操作无法暂停
operationCount:记录队列里面的操作个数,但是只会记录没有被队列调度的和调度了但是没有执行完的操作
一旦先挂起队列,再添加操作到队列,这个操作是可以成功的添加进队列,但是无法被队列调度执行
*/
-(IBAction)zanting:(id)sender{
//当队列里面没有操作时,不需要挂起队列
if(self.queue.operationCount == 0){
return;
}
//使队列暂停调度任务
self.queue.suspended = YES;
NSLog(@"暂停 %tu",self.queue.operationCount);
}
-(void)opDemo{
for (int i = 0; i < 20; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
//模拟网络延迟:仅仅是为了放大队列最大并发数的执行的效果
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%zd - %@",i,[NSThread currentThread]);
}];
[self.queue addOperation:op];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
操作间的依赖
@interface ViewController ()
@property (nonatomic,strong) NSOperationQueue *queue;
@end
//需求:登录- 付费- 下载- 通知用户
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = [[NSOperationQueue alloc] init];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self opDemo];
}
-(void)opDemo{
//登录
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"登录 :%@",[NSThread currentThread]);
}];
//付费
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"付费 :%@",[NSThread currentThread]);
}];
//登录
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载 :%@",[NSThread currentThread]);
}];
//登录
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"通知用户 :%@",[NSThread currentThread]);
}];
//op4应该被添加到主队列:因为不能把同一个操作分别添加到两个队列
//添加依赖关系
/*
一定要先建立依赖关系,再把操作添加到队列
可以跨队列建立依赖关系
不能建立循环依赖
不能把同一个操作分别添加到两个队列
*/
[op2 addDependency:op1];//付费依赖登录
[op3 addDependency:op2];//下载依赖付费
[op4 addDependency:op3];//通知用户依赖下载
//批量把操作添加到队列
//waitUntilFinished:不用等待前面的四个异步任务执行完,就可以执行后面的代码
[self.queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];
[[NSOperationQueue mainQueue] addOperation:op4];
NSLog(@"后面的代码");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end