阻塞barrier 延迟 一次性 单例 调度组 NSOperation 同时并发数 依赖操作
模拟网络延迟
[NSThread sleepForTimeInterval:1.0];
GCD阻塞
使用GCD的barrier,必须使用自定义的并发队列
自定义的并发队列
dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_CONCURRENT);
dispatch_barrier_async(queue, ^{
NSLog(@"barrier %@ - %@",name,[NSThread currentThread]);
[self.imagesM addObject:image];
});
当需要多线程环境下,操作线程不安全的类(属性)
如在多个线程中向一个NSArray可变数组添加数据
需要如上代码所示 使用barrier会单开一个线程单独的操作线程非安全的类
GCD延迟执行
延迟默认是异步执行的
dispatch_after(when, queue, block);
如上图所示需要三个参数 以下是完整版
// 参数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);
GCD一次性执行
代码演示
-
(void)onceDemo02
{
for (NSInteger i = 0; i < 1000; i++) {NSLog(@"asdfghjkasdfghjasdfghj"); dispatch_async(dispatch_get_global_queue(0, 0), ^{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"鸟哥"); }); });
}
}
以上代码中"鸟哥"只执行一次
onceToken被保存在静态存储区,**地址唯一**;
有个初始值是0;当第一次执行时,会检测初始值是否为0;
如果是0就执行代码块;
反之,就不执行代码块;
提示,当第一次执行完之后,会修改onceToken的初始值为非0的值;
#once的使用场景 : 单例模式的设计
**单例模式 **
有一个全局访问点
对象有且只有一个
保存在静态存储区
生命周期和APP一样长
**单例的使用场景**
在APP中,必须有且只有一个对象的类,要设计成单例模式 (音乐播放器...)
在APP开发中,有些类会经常频繁的使用,比如一些工具类(网络请求工具类,数据库管理工具类...)
**单例的缺点**
只要创建了单例对象,就会一直占用着一块内存,知道程序退出,单例对象的内存空间才释放
单例能过多的使用,滥用;
单例分两种 : 懒汉式和饿汉式;实际开发使用懒汉式就够了;饿汉式仅作了解
面试时,有可能手写单例
单例是一种设计模式而已,类似于MVC...设计模式;
实际开发中,不需要重写根开辟内存空间和实例化相关的任何方法 如allocwithzone
// 懒汉式单例 : 尽量创建的晚;开发中单例的创建方式
-
(instancetype)sharedTool
{
// 定义静态对象
static id instance;static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance =[[self alloc] init];
});return instance;
}
#CGD调度组
// 组
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]);
});
// 监听group里面的任务是否执行完 : 一般检测到下载结束之后,在这里面刷新UI的操作
// dispatch_group_notify 是在子线程执行的,需要回到主线程刷新UI
// 一旦检测到group里面的标记没有了,就开始自动调用dispatch_group_notify
方法
dispatch_group_notify(group, queue, ^{
NSLog(@"图片下载完了吗? %@",[NSThread currentThread]);
写回到主线程刷新UI的操作
});
#NSOperation
1.他是一个抽象类,无法直接使用;
因为只有定义,无实现;
实现交给子类;
他的作用就是作为父类,约束子类"共有"的属性和方法
2.子类
NSInvocationOperation
NSBlockOperation
自定义NSOpration
3.队列NSOperationQueue
4.GCD的核心概念 : 将任务添加到队列
5.OP的核心概念 : 将操作添加到队列
** 6.OP的使用步骤**
创建队列 默认并发的
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
创建操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo:) object:@(i)];
将操作添加到队列
[queue addOperation:op];
调用(在当前线程)
[op start];
线程间通信 : 常见代码
已在.m文件中创建属性NSOperationQueue *queue;
并在- (void)viewDidLoad 中实例化
[self.queue addOperationWithBlock:^{
NSLog(@"假装在下载... %@",[NSThread currentThread]);
// 如果异步任务执行后,拿到结果.就通知主线程刷新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"假装在刷新UI... %@",[NSThread currentThread]);
}];
}];
#操作服务质量(优先级)和监听操作是否结束
// 操作1
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"op1 %zd - %@",i,[NSThread currentThread]);
}
}];
// 设置op1的服务质量最高 : 服务质量等价于优先级,决定了队列里面的某些操作有更多的机会被队列调度执行
op1.qualityOfService = NSQualityOfServiceUserInteractive;
// 监听某个操作是否执行结束 : 一旦操作执行完毕,底层会自动回调completion
op1.completionBlock= ^{
NSLog(@"op1执行完了吗? %@",[NSThread currentThread]);
};
// 把操作添加到队列
[self.queue addOperation:op1];
#队列的最大并发数
即系统同时执行的线程数量
// 实例化队列
self.queue = [[NSOperationQueue alloc] init];
// 设置队列最大并发数
self.queue.maxConcurrentOperationCount = 2;
**暂停任务**
继续则赋值为YES即可
// 当队列里面没有操作时,不需要挂起队列
if (self.queue.operationCount == 0) {
return;
}
// 使队列暂停调度任务
self.queue.suspended = YES;
1.正在执行的操作,无法暂停
2.operationCount : 是记录队列里面的操作个数,但是,只会记录没有被队列调度的和调度了但是没有执行完的操作
3.一旦先挂起队列,再添加操作到队列,这个操作是可以成功的添加进队列;但是,无法被队列调度执行
**取消 / 移除队列里面所有的操作**
[self.queue cancelAllOperations];
1.正在执行的操作无法取消
2.如果你非要取消正在执行的操作,需要自定义NSOperation
3.取消全部操作,会有一定的时间延迟;具体延迟多久,是系统自己算的
4.为什么会有延迟?
队列一旦调用了 cancelAllOperations
,队列会遍历自己里面的所有操作
每遍历出一个操作,就会调用cancel
方法
一旦操作对象调用了cancel
方法,那么操作对象的cancelled
属性,就会被设置成YES;表示该操作不能正常执行
一旦队列发现,操作对象的cancelled
属性为YES;就不会调度其执行;会移除掉,operationCount不会对其记录
#操作间的依赖
模拟需求 : 登录 --> 付费 --> 下载 --> 通知用户
[付费 addDependency: 登录] ---> 付费依赖于登录
**注意点**
一定要先建立依赖关系,再把操作添加到队列
可以跨队列建立依赖关系
不能建立循环依赖
不能把同一个操作分别添加到两个队列
-
(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]);
}];/*
1.一定要先建立依赖关系,再把操作添加到队列
2.可以跨队列建立依赖关系
3.不能建立循环依赖
4.不能把同一个操作分别添加到两个队列
*/// 添加依赖关系
[op2 addDependency:op1]; // 付费依赖登录
[op3 addDependency:op2]; // 下载依赖付费
[op4 addDependency:op3]; // 通知用户依赖下载// 循环依赖
// [op1 addDependency:op4];// 批量把操作添加到队列
// waitUntilFinished : 不用等待前面的四个异步任务执行完,就可以执行后面的代码
[self.queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];// op4应该被添加到主队列 : 不能把同一个操作分别添加到两个队列
[[NSOperationQueue mainQueue] addOperation:op4];NSLog(@"后面的代码");
}