多线程之operation

1.NSOperation简介
NSOperation是苹果公司对于GCD的封装,比GCD更简单易用、代码可读性也更高,弱化了串行,并行,同步,异步这些名词,并且还有一些新的功能,NSOperation,拥有很多的API,依赖,可以通过KVO来检测NSOperation的状态 (isExecuted, isFinished, isCancelled),和可满足更高的需求等,我会在下面一一介绍。
对比GCD我们会发现,NSOperation 相当于GCD 当中的任务,NSOperationQueue 相当于GCD中的队列。
在NSOperationQueue 当中包含了串行和并行的功能。通过maxConcurrentOperationCount是否等于1来决定是串行队列还是并行队列
而NSOperation单独使用时,是相当于GCD的同步任务,没有开辟新线程的能力,只有配合NSOperationQueue才能实现异步。
使用步骤也很简单,分为三步:
1 先将需要执行的操作封装到一个NSOperation对象中。(创建任务)
2 创建NSOperationQueue对象。(创建队列)
3 将任务加入到队列中:然后将NSOperation对象添加到NSOperationQueue中。
4系统就会自动将NSOperationQueue中的NSOperation取出来,在新线程中执行操作。

2、创建任务 NSOperation
NSOperation 是一个抽象类,不能封装任务,我们只有通过它的两个子类(NSInvocationOperation 和 NSBlockOperation)或者自定义operation来封装任务。
(1)使用NSInvocationOperation 和 NSBlockOperation子类
NSInvocationOperationNSBlockOperation 都是NSoperation的子类,用法也差不多,只不过一个是调方法,一个是block

//NSInvocationOperation
- (void)invocationOperation {
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationRun) object:nil];
    [invocationOperation start]; //开始执行
}

- (void)invocationOperationRun {
    
}


//NSBlockOperation
- (void)blockOperation {
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        //执行任务
    }];
    [blockOperation start];//开始执行
}

NSInvocationOperation 和 NSBlockOperation 比较简单,就不多说了,下面主要介绍自定义Operation
(2)自定义Operation
上面的两种Operation有时候并不能满足我们的需求,比如我们需要将任务的下载,网络请求和其他操作封装在任务里的时候,就需要自定义Operation了。
值得注意的是,我们在自定义Operation的时候,通常会重写 main 或者 start 方法,这里要特别说明:如果想使用同步,那么就重写main方法,并且不将自定义Operation加入队列中,如果想要使用异步,那必须将逻辑写到start,或者main方法中,然后加入到队列中。
通过实验得知,如果同时重写start和main两个方法,系统只会调用start方法

#import "XQOperation.h"

@implementation XQOperation

- (void)main {
    for (int i = 0;i < 10; i ++) {
        NSLog(@“…..start i:%d  %@",i,[NSThread currentThread]);
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    
    XQOperation *operation = [[XQOperation alloc] init];
//    [operation start];

    [operationQueue addOperation:operation];
}

2018-01-25 17:46:05.896433+0800 XQOperation[44959:6773423] .....start i:0  {number = 4, name = (null)}
2018-01-25 17:46:05.896536+0800 XQOperation[44959:6773423] .....start i:1  {number = 4, name = (null)}
2018-01-25 17:46:05.896595+0800 XQOperation[44959:6773423] .....start i:2  {number = 4, name = (null)}
2018-01-25 17:46:05.896651+0800 XQOperation[44959:6773423] .....start i:3  {number = 4, name = (null)}
2018-01-25 17:46:05.896708+0800 XQOperation[44959:6773423] .....start i:4  {number = 4, name = (null)}
2018-01-25 17:46:05.896765+0800 XQOperation[44959:6773423] .....start i:5  {number = 4, name = (null)}
2018-01-25 17:46:05.896821+0800 XQOperation[44959:6773423] .....start i:6  {number = 4, name = (null)}
2018-01-25 17:46:05.898894+0800 XQOperation[44959:6773423] .....start i:7  {number = 4, name = (null)}
2018-01-25 17:46:05.898986+0800 XQOperation[44959:6773423] .....start i:8  {number = 4, name = (null)}
2018-01-25 17:46:05.899555+0800 XQOperation[44959:6773423] .....start i:9  {number = 4, name = (null)}

3.创建队列和任务添加

   //创建其他队列,加入到其他队列的任务,都会在自动在子线程中执行
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    //创建主队列,放在主队列的任务,都会在主线程执行
    NSOperationQueue *mainOperationQueue = [NSOperationQueue mainQueue];

添加方法

1.- (void)addOperation:(NSOperation *)op;

    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];

    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationRun) object:nil];
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        //执行任务
        NSLog(@"b---- %@",[NSThread currentThread]);
    }];
    [operationQueue addOperation:invocationOperation];
    [operationQueue addOperation:blockOperation];


2.- (void)addOperationWithBlock:(void (^)(void))block;

无需先创建任务,在block中添加任务,直接将任务block加入到队列中。
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];

    [operationQueue addOperationWithBlock:^{
        //添加任务
    }];

控制串行执行和并行执行的方法
我们之前说过NSOperation弱化了串行,并行,同步,异步这些,当我们将Operation加入到OperationQueue当中的时候,就是异步操作,不加入就是 同步操作,同理,NSOperation通过maxConcurrentOperationCount一个叫最大并发数的属性来控制是串行还是并行。

(1)maxConcurrentOperationCount默认为-1,表示不进行显示,为并发队列。
(2)当maxConcurrentOperationCount 等于1的时候,是串行队列。当maxConcurrentOperationCount大于1的时候,是并发队列,和GCD一样,系统为了控制资源,这个数不会无限大,会更加手机的实际情况来调整的

    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    operationQueue.maxConcurrentOperationCount = 2;
    
    XQOperation *operation = [[XQOperation alloc] init];
    XQOperation *operation2 = [[XQOperation alloc] init];

    [operationQueue addOperation:operation];
    [operationQueue addOperation:operation2];

- (void)main {
    for (int i = 0;i < 2; i ++) {
        NSLog(@".....main i:%d  %@",i,[NSThread currentThread]);
    }
}

XQOperation[45000:6786387] .....main i:0  {number = 3, name = (null)}
2018-01-25 18:14:30.184979+0800 XQOperation[45000:6786387] .....main i:1  {number = 3, name = (null)}
2018-01-25 18:14:30.185356+0800 XQOperation[45000:6786386] .....main i:0  {number = 4, name = (null)}
2018-01-25 18:14:30.185477+0800 XQOperation[45000:6786386] .....main i:1  {number = 4, name = (null)}
 

如何实现并发的NSOperation?
(1)重写isConcurrent函数,返回YES.这个是一个并发标识
(2)重写start()函数
(3)重写 isExecuting和isFinished函数 (重写这个是为了,我们能够手动控制operation什么时候结束,通过kvo来发送消息 )

#import "XQOperation.h"

@interface XQOperation ()

@property (nonatomic, assign) BOOL finished;
@property (nonatomic, assign) BOOL executing;

@end

@implementation XQOperation

@synthesize finished = _finished;
@synthesize executing = _executing;

- (void)start {
     _executing = YES;
    for (int i = 0;i < 2; i ++) {
        NSLog(@".....start i:%d  %@",i,[NSThread currentThread]);
    }
    _executing = NO;
    _finished = YES;
    if (_finished) {
        NSLog(@"....");
    }
}

- (void)setFinished:(BOOL)finished {
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

//重写NSOperation方法,标识这是一个并发任务
- (BOOL)isConcurrent {
    return YES;
}

@end

NSOperationQueue 里面执行 NSOperation
当NSOperation被加入到 队列里面的时候,他就一定是并发设计了,因为,queue会为每一个加入到队列里的operation创建一个线程来运行start()函数,这样每个start都分布在不同的线程里面来实现并发任务,而且我们可以通过maxConcurrentOperationCount来指定对打的并发数,如果是1,那么就是串行队列了

4.NSOperation的依赖执行

[operationB addDependency:operationA]

在并发情况下这个状态量是由你自己设定的, 比如operationA是用来异步下载一张图片, 那么只有图片下载完成之后或者超过timeout下载失败之后, isFinished状态量被标记为YES, 这时Queue才会从队列里面移除operationA, 并启动operationB.

5.值得注意的地方
(1)取消
当从外部来取消operation的时候,不会马上cancel,默认情况下,都不能取消已经正在执行的operation,只能取消在队列中等待的operation。
(2)并发和并行
并行和并发都是用来让不同的任务可以"同时执行”,只是并行是假同时,只是看上去像同时发生,其实不是真正的同时执行,而并发是真同时
(a)首先如果你的CPU是单核的, 为了实现"同时"执行T1和T2, 那只能分时执行, CPU执行一会儿T1后马上再去执行T2, 切换的速度非常快(这里的切换也是需要消耗资源的, context switch), 以至于你以为T1和T2是同时执行了(但其实同一时刻只有一个任务占有着CPU).
(b)如果你是多核CPU, 那么恭喜你, 你可以真正同时执行T1和T2了, 在同一时刻CPU的核心core1执行着T1, 然后core2执行着T2,
总的来说,我们平常说的并发编程是包括并发和并行的,系统会判断在某一个时刻是否有可用的core,但是你可以并发的方式设计你的代码。

(3)关于RunLoop和NSThread
(a)RunLoop是即使你什么都不做,放在那里它会一直在,不会退出,当我们需要在子线程中做一些下载任务,当start函数返回,子线程就退出了,但是当下载完成回调的时候,线程已经没有了,所以回调也就接收不到了,为了保证子线程一直存在,我们需要在子线程加入RunLoop
每个thread中默认会有一个runLoop对象,主线程的runLoop对象是运行着的,用户自己创建的子线程runLoop对象默认是没有启动的。


+ (void)networkRequestThreadEntryPoint:(id)__unused object 
{
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}
+ (NSThread *)networkRequestThread 
{
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });
    return _networkRequestThread;
}
- (void)start 
{
    [self.lock lock];
    if ([self isCancelled]) {
        [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    } else if ([self isReady]) {
        self.state = AFOperationExecutingState;
        [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    [self.lock unlock];
}

AFNetworking创建了一个新的子线程(在子线程中调用NSRunLoop *runloop = [NSRunLoop currentRunLoop]; 获取RunLoop对象的时候, 就会创建RunLoop), 然后把它加到RunLoop里面来保证它一直运行.
这边我们可以简单的判断下当前start()的线程是子线程还是主线程, 如果是子线程则调用[NSRunLoop currentRunLoop]创新RunLoop, 否则就直接调用[NSRunLoop mainRunLoop], 当然在主线程下就没必要调用[runLoop run]了, 因为它本来就是一直run的.

我们还可以使用CFRunLoop来启动和停止RunLoop

[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop]
                           forMode:NSRunLoopCommonModes];
CFRunLoopRun();

等到该Operation结束的时候, 一定要记得调用CFRunLoopStop()停止当前线程的RunLoop, 让当前线程在operation finished之后可以退出

你可能感兴趣的:(多线程之operation)