多线程03 阻塞barrier 延迟 一次性 单例 调度组 NS

2016-10-20  本文已影响0人  xwf_code

阻塞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一次性执行
代码演示
以上代码中"鸟哥"只执行一次
onceToken被保存在静态存储区,**地址唯一**;
有个初始值是0;当第一次执行时,会检测初始值是否为0;
如果是0就执行代码块;
反之,就不执行代码块;
提示,当第一次执行完之后,会修改onceToken的初始值为非0的值;

#once的使用场景 : 单例模式的设计
**单例模式 **
有一个全局访问点
对象有且只有一个
保存在静态存储区
生命周期和APP一样长

**单例的使用场景**
在APP中,必须有且只有一个对象的类,要设计成单例模式 (音乐播放器...)
在APP开发中,有些类会经常频繁的使用,比如一些工具类(网络请求工具类,数据库管理工具类...)

**单例的缺点**
只要创建了单例对象,就会一直占用着一块内存,知道程序退出,单例对象的内存空间才释放
单例能过多的使用,滥用;
单例分两种 : 懒汉式和饿汉式;实际开发使用懒汉式就够了;饿汉式仅作了解
面试时,有可能手写单例
单例是一种设计模式而已,类似于MVC...设计模式;
实际开发中,不需要重写根开辟内存空间和实例化相关的任何方法 如allocwithzone

// 懒汉式单例 : 尽量创建的晚;开发中单例的创建方式


#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: 登录] ---> 付费依赖于登录
**注意点**
一定要先建立依赖关系,再把操作添加到队列
可以跨队列建立依赖关系
不能建立循环依赖
不能把同一个操作分别添加到两个队列
上一篇下一篇

猜你喜欢

热点阅读