15 - GCD、NSOperation

2017-07-02  本文已影响23人  RadioWaves

GCD的优势 :

  1. GCD是苹果公司为多核的并行运算提出的解决方案
  2. GCD会自动利用更多的CPU内核(比如双核、四核)
  3. GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  4. 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

GCD的使用就两个步骤 :

  1. 定制任务(确定想做的事情)
  2. 将任务添加到队列中GCD会自动将队列中的任务取出,放到对应的线程中执行,任务的取出遵循队列的FIFO(First in First out)原则:先进先出,后进后出

GCD有两个核心概念 :

概念一 : 任务 (执行什么操作)

GCD中有两个用来执行任务的函数

// 用同步的方式执行任务 :只能在当前线程中执行任务,不具备开启新线程的能力

//  sync: 同步                 queue:队列           block:任务    
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

// 用异步的方式执行任务 : 可以在新的线程中执行任务,具备开启新线程的能力

//  async: 异步                 queue:队列           block:任务   
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

概念二 : 队列 :(用来存放任务)

GCD中的队列可以分为两大类型
1: 并发队列(Concurrent Dispatch Queue):
2: 串行队列(Serial Dispatch Queue)

同步、异步、并发、串行的作用:


并发队列与串行队列

并发队列

GCD的默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建.

  1. 获得全局并发队列
// 用这个获得全局并发队列{dispatch_get_global_queue}
// 用这个获得优先级{DISPATCH_QUEUE_PRIORITY_DEFAULT}   
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
  1. 并发队列全局并发队列的优先级
define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台

并发队列 + 同步函数 : 不会开启线程

Snip20150902_4.png

并发队列 + 异步函数 : 会开启线程

Snip20150902_7.png

串行队列

GCD中获得串行有2种途径

dispatch_queue_t queue = dispatch_queue_create("c.w.z", NULL); 

串行队列 + 异步函数 : 会开启线程

Snip20150902_10.png

串行队列 + 同步函数 : 不会开启线程

Snip20150902_12.png
dispatch_queue_t queue = dispatch_get_main_queue();

主要是主队列:不会开启线程

Snip20150902_14.png

各种队列的执行效果

Snip20150831_56.png

线程间的通讯

从子线程回到主线程

执行耗时的异步操作...

dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         // 回到主线程,执行UI刷新操作
       dispatch_async(dispatch_get_main_queue(), ^{
    });
});

下载图片的操作

Snip20150902_15.png

延时执行

iOS常见的延时执行有2种方式

// 调用NSObject的方法
// 2秒后再调用self的run方法
self performSelector:@selector(run) withObject:nil afterDelay:2.0;

// 使用GCD函数
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)),`                                  
    dispatch_get_main_queue(), ^{
    // 2秒后异步执行这里的代码...       
});

// 使用NSTimer
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];

只执行一次的代码

// 运行时只执行一次
// 使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只执行1次的代码(这里面默认是线程安全的)
});

队列组

Warning:有这么1种需求:
首先:分别异步执行2个耗时的操作
其次:等2个异步操作都执行完毕后,再回到主线程执行操作
如果想要快速高效地实现上述需求,可以考虑用队列组

dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)^{
    // 执行1个耗时的异步操作
});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行1个耗时的异步操作
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的异步操作都执行完毕后,回到主线程...
});

代码详细实现:

需求:在子线程中下载2张图片.分别显示在屏幕的左右.
分析:由于下载图片属于耗时操作,所以要在子线程中操作.等2张图片都下载好后,在回到主线程加载.

// 点击屏幕后调用
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self group];
}

// 队列组
- (void)group
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 创建一个队列组
    dispatch_group_t group = dispatch_group_create();
    
    // 1.下载图片1
    dispatch_group_async(group, queue, ^{
        // 图片的网络路径
        NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
        
        // 加载图片
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        // 生成图片
        self.image1 = [UIImage imageWithData:data];
    });
    
    // 2.下载图片2
    dispatch_group_async(group, queue, ^{
        // 图片的网络路径
        NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
        
        // 加载图片
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        // 生成图片
        self.image2 = [UIImage imageWithData:data];
    });
    
    // 3.将图片1、图片2合成一张新的图片
    dispatch_group_notify(group, queue, ^{
        // 开启新的图形上下文
        UIGraphicsBeginImageContext(CGSizeMake(100, 100));
        
        // 绘制图片
        [self.image1 drawInRect:CGRectMake(0, 0, 50, 100)];
        [self.image2 drawInRect:CGRectMake(50, 0, 50, 100)];
        
        // 取得上下文中的图片
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        
        // 结束上下文
        UIGraphicsEndImageContext();
        
        // 回到主线程显示图片
        dispatch_async(dispatch_get_main_queue(), ^{
            // 4.将新图片显示出来 
            self.imageView.image = image;
        });
    });
}

显示结果

barrier

GCD中还有个用来执行任务的函数barrier(栅栏/障碍物)

dispatch_barrier_async(dispatch_queue_t queue>, ^(void)block)
Paste_Image.png

他会在执行完 barrier上面的1和2后在执行barrier后面的3和4


NSOperation:

是在GCD的基础上进行的一层面向对象的包装.

核心概念:

任务和队列.和GCD基本上是一样的,只不过更加的面向对象.用起来比较爽.

NSOperation的作用

NSOperation的子类

使用NSOperation子类的方式有3种:

  1. NSInvocationOperation,
  2. NSBlockOperation,
  3. 自定义子类继承NSOperation,实现内部相应的方法;

NSOperation和NSOperationQueue实现多线程的具体步骤:

1 - NSInvocationOperation(比较少用)

// 1.创建NSInvocationOperation对象
- (id)initWithTarget:(id)target selector:(SEL)sel object:
- (id)arg;

// 2. 调用start方法开始执行操作
    - (void)start;
一旦执行操作,就会调用target的sel方法
  1. 默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作
  2. 只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作


    Snip20150920_62.png

2 - NSBlockOperation

// 1. 创建NSBlockOperation对象
+ (id)blockOperationWithBlock:(void (^)(void))block;

// 2. 通过addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void (^)(void))block;

3 -自定义NSOperation

NSOperationQueue

// 添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;

NSOperationQueue的队列类型

主队列:
凡是放到了主队列的任务(NSOperation),都会放到主线程中执行
[NSOperationQueue mainQueue];

其他队列(串行/并发):
同时包含:串行 / 并发功能
添加这种队列中的任务,就会自动放到子线程中执行
[NSOperationQueue alloc] init];

最大并发数

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

// 最大并发数的相关方法
-  (NSInteger)maxConcurrentOperationCount;  
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

队列的取消、暂停、恢复

// 取消队列的所有操作
- (void)cancelAllOperations;    

__提示:
也可以调用NSOperation的- (void)cancel方法取消单个操作

// 暂停和恢复队列(YES代表暂停队列,NO代表恢复队列)
- (void)setSuspended:(BOOL)b; 
- (BOOL)isSuspended;

经常通过`- (BOOL)isCancelled`方法检测操作是否被取消,对取消做出响应

suspended 暂定/挂起

__warning:#cancel     
    官方建议:执行完一段耗时操作的后面最好加上是否点击了取消的判断.
    人为控制取消操作.以免,全部执行完,还没有取消掉.

操作优先级

设置NSOperation在queue中的优先级,可以改变操作的执行优先级
- (NSOperationQueuePriority)queuePriority;
- (void)setQueuePriority:(NSOperationQueuePriority)p;

##优先级的取值
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8

操作依赖

比如一定要让操作A执行完后,才能执行操作B,可以这么写

// 操作B依赖于操作A
operationB addDependency:operationA;

操作的监听

// 可以监听一个操作的执行完毕
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;

自定义NSOperation的步骤很简单

重写`-(void)main`方法,在里面实现想执行的任

重写- (void)main方法的注意点:

  1. 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
  2. 经常通过-(BOOL)isCancelled方法检测操作是否被取消,对取消做出响应

GCD和NSOperation的区别

NSOperation:

1 - NSOperation拥有更多的函数可用,具体查看API。
2 - 在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。
3 - 有KVO,可以监测operation是否
--------------------------正在执行isExecuted
--------------------------是否结束isFinished
--------------------------是否取消isCanceld;
4 - NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。

NSOperationQueue的队列类型

GCD:

1 - 主要与block结合使用。代码简洁高效。
2 - GCD也可以实现复杂的多线程应用,主要是建立个个线程时间的依赖关系这类的情况,但是需要自己实现相比NSOperation要复杂。具体使用哪个,依需求而定。

GCD的队列类型

从个人使用的感觉来看,比较合适的用法是:除了依赖关系尽量使用GCD,因为苹果专门为GCD做了性能上面的优化。


GCD实现单例模式

单例模式 :

其实就是一种设计模式

适用场合

单例模式一般会在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次)的时候使用.

比如:

比如:
Person *p1 = [Person alloc] init];
Person *p2 = [Person alloc] init];
Person *p3 = [Person alloc] init];
Person *p4 = [Person alloc] init];
Snip20150920_33.png

从上图我们可以看到:

那么我们怎么样来实现单例模式呢?

第一种方法:重写+ allocWithZone

  • 之所以我们要实现单例模式,是希望无论alloc了多少次,都只产生一份内存
Snip20150920_36.png

接下来我们来通过打印属性值来验证下:

Snip20150920_37.png
Snip20150920_39.png
Snip20150920_38.png

第二种方法:[UIApplication sharedApplication]

Snip20150920_42.png
Snip20150920_45.png
Snip20150920_46.png

第三种方法:copy

Snip20150920_48.png

那么,我们应该这样做

Snip20150920_49.png
Snip20150920_50.png

将以上的结合起来才属于一套完整的单例模式

Snip20150920_52.png

那么以后一个工程中可能会有很多单例,总不能一个一个改吧?

那么我们可以将单例弄成宏的形式,以后遇到了直接拖入工程就可以了


Snip20150920_58.png

那么 我们来看下效果:

.h文件

Snip20150920_59.png

.m文件

Snip20150920_60.png

实现的效果

Snip20150920_61.png

单例模式在ARC\MRC环境下的写法有所不同

需要编写2套不同的代码

可以用宏判断是否为ARC环境

#if __has_feature(objc_arc)
// ARC
#else
// MRC
#endif

ARC中,单例模式的实现

1. 在.m中保留一个全局的`static`的实例

static id _instance;

// 重写allocWithZone:方法,在这里创建唯一的实例(注意线程安全)  
+(id)allocWithZone:(struct _NSZone *)zone                      
{
    @synchronized(self) {
        if (!_instance) {                                
        _instance = [super allocWithZone:zone];          
        }
    }   
    return _instance;                                       
}

2. 提供1个类方法让外界访问唯一的实例
        
+ (instancetype)sharedSoundTool{
    @synchronized(self) {
        if (!_instance) {                                
        _instance = [[self alloc] init];                 
        }
    }
    return _instance;                                       
}

3. 实现copyWithZone:方法
- (id)copyWithZone:(struct _NSZone *)zone 
        {
            return _instance;
        }

MRC中,单例模式的实现(比ARC多了几个步骤)

// 实现内存管理方法
- (id)retain { 
     return self; 
}

- (NSUInteger)retainCount{ 
 return 1; 
}

- (oneway void)release {
}

- (id)autorelease { 
     return self;
}
上一篇 下一篇

猜你喜欢

热点阅读