iOSiOS知识gcd

iOS开发之多线程编程总结(三)

2016-11-08  本文已影响2072人  Dely

前言

前段时间的心病落下帷幕后,一大波需求向我迎来,忙的我最近没时间更新博客了,只能在闲暇的时间吹吹牛逼了。这篇博客主要讲解NSOperation的一些知识。

busy Time.jpg

1. NSOperation简介

NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更简单易用、代码可读性也更高。

因为NSOperation是基于GCD的,那么使用起来也和GCD差不多,其中,NSOperation相当于GCD中的任务,而NSOperationQueue则相当于GCD中的队列。NSOperation实现多线程的使用步骤分为三步:

之后,系统就会自动将NSOperationQueue中的NSOperation取出来,在新线程中执行操作。

下面就跟我一起学习NSOperation的相关知识点。

2. NSOperation和NSOperationQueue的基本使用

1. 创建任务

在默认情况下,NSOperation 是同步执行的,也就是说会阻塞当前线程直到任务完成。

NSOperation 本身是一个抽象类,不能直接实例化,因此,如果我们想要使用它来执行具体任务的话,就必须使用系统预定义的两个子类NSInvocationOperationNSBlockOperation或者创建自己的子类

1.使用子类NSInvocationOperation

NSInvocationOperation:我们可以通过一个 object 和 selector 非常方便地创建一个 NSInvocationOperation ,这是一种非常动态和灵活的方式。

NSInvocationOperation方法:

- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;

NSInvocationOperation-Demo:

- (void)invocationOperation{
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    op.completionBlock = ^{
        NSLog(@"任务完成后回调block");
    };
    
    [op start];
}
- (void)run{
    NSLog(@"------%@", [NSThread currentThread]);
}

2.使用子类NSBlockOperation

** NSBlockOperation:**我们可以使用 NSBlockOperation 来并发执行一个或多个 block ,只有当一个 NSBlockOperation 所关联的所有 block 都执行完毕时(会阻塞当前线程),这个 NSBlockOperation 才算执行完成,有点类似于 dispatch_group 的概念。

** NSBlockOperation方法:**

+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;

** NSBlockOperation-Demo:**

- (void)blockOperationAddTask{
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"------%@", [NSThread currentThread]);
    }];
    for (int i = 0; i < 5; i++) {
        [op addExecutionBlock:^{
            NSLog(@"%d------%@", i,[NSThread currentThread]);
        }];
    }
    
    [op start];
}

打印结果:

2016-11-07 21:12:34.531 ThreadDemo[1667:25954] ------<NSThread: 0x600000061d80>{number = 1, name = main}
2016-11-07 21:12:34.531 ThreadDemo[1667:25954] 3------<NSThread: 0x600000061d80>{number = 1, name = main}
2016-11-07 21:12:34.532 ThreadDemo[1667:25954] 4------<NSThread: 0x600000061d80>{number = 1, name = main}
2016-11-07 21:12:34.531 ThreadDemo[1667:28309] 2------<NSThread: 0x6000000747c0>{number = 8, name = (null)}
2016-11-07 21:12:34.531 ThreadDemo[1667:28308] 1------<NSThread: 0x600000077280>{number = 7, name = (null)}
2016-11-07 21:12:34.531 ThreadDemo[1667:28307] 0------<NSThread: 0x608000079500>{number = 6, name = (null)}

** NSBlockOperation注意点:**

3. 定义继承自NSOperation的子类

NSOperation的子类:当系统预定义的两个子类 NSInvocationOperationNSBlockOperation 不能很好的满足我们的需求时,我们可以自定义自己的 NSOperation 子类,添加我们想要的功能。我们可以自定义非并发和并发两种不同类型的 NSOperation 子类,而自定义一个前者要比后者简单得多。我们先来一个简单的非并发的NSOperation 子类,并发的NSOperation的单独在后面讲解!

非并发的NSOperation 子类Demo:

先定义一个继承自NSOperation的子类,重写main方法
JYSerialOperation.h

#import <Foundation/Foundation.h>

typedef void (^JYCompletionBlock)(NSData *imageData);

@interface JYSerialOperation : NSOperation

@property (nonatomic, copy) JYCompletionBlock comBlock;

@end

JYSerialOperation.m

#import "JYSerialOperation.h"

@implementation JYSerialOperation

- (void)main{
    @autoreleasepool {
        if (self.isCancelled) {
            return;
        }
        NSURL *url=[NSURL URLWithString:@"https://p1.bpimg.com/524586/475bc82ff016054ds.jpg"];
        NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
        
        if (!imageData) {
            imageData = nil;
        }
        
        if (self.isCancelled) return;
        
        [self performSelectorOnMainThread:@selector(completionAction:) withObject:imageData waitUntilDone:NO];
    
    }
}

- (void)completionAction:(NSData *)imageData{
    if (self.comBlock) {
        self.comBlock(imageData);
    }
}

@end

使用的时候导入头文件然后调用:

//在主线程中执行并没有开辟线程
JYSerialOperation *op = [[JYSerialOperation alloc] init];
[op start];

op.comBlock = ^(NSData *imageData){
    UIImage *image = [UIImage imageWithData:imageData];
    self.imageView.image = image;
};

2. 创建队列

和GCD中的并发队列、串行队列略有不同的是:NSOperationQueue一共有两种队列:主队列、其他队列。其中其他队列同时包含了串行、并发功能。下边是主队列、其他队列的基本创建方法和特点。

主队列

NSOperationQueue *queue = [NSOperationQueue mainQueue];

其他队列(非主队列)

3. 把任务加入到队列中

只要将任务加入到队列中,就不要执行start方法,队列会负责调度任务自动执行start方法。加入队列的方法如下:

- (void)addOperation:(NSOperation *)op;//添加单个任务
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);//添加任务数组

- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);//添加单个任务

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

- (void)addOperationToQueue{
    
    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 2. 创建操作
    // 创建NSInvocationOperation
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2) object:nil];
    // 创建NSBlockOperation
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"op2---->%d-----%@", i,[NSThread currentThread]);
        }
    }];
    
    // 3. 添加操作到队列中:addOperation:
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
}

- (void)run2{
    for (int i = 0; i < 5; i++) {
        NSLog(@"op1---->%d-----%@",i, [NSThread currentThread]);
    }
}
2016-11-07 22:03:44.125 ThreadDemo[2761:55463] op1---->0-----<NSThread: 0x60000007f980>{number = 3, name = (null)}
2016-11-07 22:03:44.125 ThreadDemo[2761:55484] op2---->0-----<NSThread: 0x60000007fd80>{number = 4, name = (null)}
2016-11-07 22:03:44.126 ThreadDemo[2761:55484] op2---->1-----<NSThread: 0x60000007fd80>{number = 4, name = (null)}
2016-11-07 22:03:44.126 ThreadDemo[2761:55463] op1---->1-----<NSThread: 0x60000007f980>{number = 3, name = (null)}
2016-11-07 22:03:44.127 ThreadDemo[2761:55484] op2---->2-----<NSThread: 0x60000007fd80>{number = 4, name = (null)}
2016-11-07 22:03:44.127 ThreadDemo[2761:55463] op1---->2-----<NSThread: 0x60000007f980>{number = 3, name = (null)}
2016-11-07 22:03:44.127 ThreadDemo[2761:55484] op2---->3-----<NSThread: 0x60000007fd80>{number = 4, name = (null)}
2016-11-07 22:03:44.128 ThreadDemo[2761:55463] op1---->3-----<NSThread: 0x60000007f980>{number = 3, name = (null)}
2016-11-07 22:03:44.128 ThreadDemo[2761:55463] op1---->4-----<NSThread: 0x60000007f980>{number = 3, name = (null)}
2016-11-07 22:03:44.128 ThreadDemo[2761:55484] op2---->4-----<NSThread: 0x60000007fd80>{number = 4, name = (null)}

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


- (void)addOperationWithBlockToQueue{
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 设置最大并发操作数
    //    queue.maxConcurrentOperationCount = 1;// 就变成了串行队列
    queue.maxConcurrentOperationCount = 5;

    for (int i = 0; i < 5; i++) {
        [queue addOperationWithBlock:^{
            NSLog(@"%d-----%@",i, [NSThread currentThread]);
        }];
    }
    
}
2016-11-07 22:13:14.189 ThreadDemo[2933:60785] 2-----<NSThread: 0x600000274840>{number = 10, name = (null)}
2016-11-07 22:13:14.189 ThreadDemo[2933:60814] 0-----<NSThread: 0x608000260f00>{number = 14, name = (null)}
2016-11-07 22:13:14.189 ThreadDemo[2933:60803] 1-----<NSThread: 0x600000275600>{number = 11, name = (null)}
2016-11-07 22:13:14.189 ThreadDemo[2933:60812] 4-----<NSThread: 0x600000274a40>{number = 13, name = (null)}
2016-11-07 22:13:14.189 ThreadDemo[2933:60695] 3-----<NSThread: 0x608000260a00>{number = 9, name = (null)}

可以看出addOperationWithBlock:和NSOperationQueue能够开启新线程,进行并发执行。

3. 队列的重要属性maxConcurrentOperationCount

maxConcurrentOperationCount队列的最大并发数,也就是当前执行队列的任务时,最多开辟多少条线程!具体开多少条线程是由底层线程池来决定。

队列是串行还是并发就是由maxConcurrentOperationCount来决定

代码参考上一个- (void)addOperationWithBlock:(void (^)(void))block ;Demo,修改最大并发数即可测试,结果如下:

//maxConcurrentOperationCount=1
2016-11-08 10:05:34.748 ThreadDemo[1224:14753] 0-----<NSThread: 0x600000263100>{number = 4, name = (null)}
2016-11-08 10:05:34.749 ThreadDemo[1224:14753] 1-----<NSThread: 0x600000263100>{number = 4, name = (null)}
2016-11-08 10:05:34.749 ThreadDemo[1224:14753] 2-----<NSThread: 0x600000263100>{number = 4, name = (null)}
2016-11-08 10:05:34.750 ThreadDemo[1224:14754] 3-----<NSThread: 0x6000002635c0>{number = 3, name = (null)}
2016-11-08 10:05:34.750 ThreadDemo[1224:14754] 4-----<NSThread: 0x6000002635c0>{number = 3, name = (null)}

注意点:

4. 任务的操作依赖

通过配置依赖关系,我们可以让不同的 operation 串行执行,正如我们上面刚刚提到的最大并发数为1时串行执行(但是顺序不一定会是我们想要的顺序),一个 operation 只有在它依赖的所有 operation 都执行完成后才能开始执行。配置 operation 的依赖关系主要涉及到NSOperation 类中的以下两个方法:

- (void)addDependency:(NSOperation *)op;//添加依赖
- (void)removeDependency:(NSOperation *)op;//删除依赖

任务的操作依赖Demo:

#pragma mark --------------操作依赖
- (void)operateDependency{

    NSMutableArray *array = [NSMutableArray array];
    
    //创建任务
    for (int i = 0; i < 10; i++) {
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"________第%d个任务%@____",i,[NSThread currentThread]);
        }];
        op.name = [NSString stringWithFormat:@"op%d",i];
        
        [array addObject:op];
    }
    
    //创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    queue.name = @"queue";
    
    //设置依赖 可以跨队列依赖。
    for (int i = 0; i < array.count - 1; i++) {
        //依次依赖,下面相当于同步执行了
        NSBlockOperation *op1 = [array objectAtIndex:i];
        NSBlockOperation *op2 = [array objectAtIndex:i+1];
        [op2 addDependency:op1];
        
//        //修改 Operation 在队列中的优先级
//        if (i == 6) {
//            [op1 setQueuePriority:NSOperationQueuePriorityVeryHigh];
//        }
//
//        if (i > 4) {
//            //删除依赖
//            [op2 removeDependency:op1];
//        }
    }
    
//    //需求:第5个任务完成后取消队列任务
//    NSBlockOperation *op1 = [array objectAtIndex:4];
//    op1.completionBlock = ^{
//        //取消队列中未执行的所有任务
//        [queue cancelAllOperations];
//    };
    
    //添加任务到队列中
    [queue addOperations:array waitUntilFinished:NO];
    
}

打印结果:

2016-11-08 10:37:37.505 ThreadDemo[1224:29218] ________第0个任务<NSThread: 0x600000263340>{number = 6, name = (null)}____
2016-11-08 10:37:37.506 ThreadDemo[1224:29228] ________第1个任务<NSThread: 0x608000263080>{number = 7, name = (null)}____
2016-11-08 10:37:37.506 ThreadDemo[1224:29228] ________第2个任务<NSThread: 0x608000263080>{number = 7, name = (null)}____
2016-11-08 10:37:37.507 ThreadDemo[1224:29218] ________第3个任务<NSThread: 0x600000263340>{number = 6, name = (null)}____
2016-11-08 10:37:37.507 ThreadDemo[1224:29228] ________第4个任务<NSThread: 0x608000263080>{number = 7, name = (null)}____
2016-11-08 10:37:37.508 ThreadDemo[1224:29218] ________第5个任务<NSThread: 0x600000263340>{number = 6, name = (null)}____
2016-11-08 10:37:37.508 ThreadDemo[1224:29218] ________第6个任务<NSThread: 0x600000263340>{number = 6, name = (null)}____
2016-11-08 10:37:37.509 ThreadDemo[1224:29228] ________第7个任务<NSThread: 0x608000263080>{number = 7, name = (null)}____
2016-11-08 10:37:37.510 ThreadDemo[1224:29218] ________第8个任务<NSThread: 0x600000263340>{number = 6, name = (null)}____
2016-11-08 10:37:37.511 ThreadDemo[1224:29228] ________第9个任务<NSThread: 0x608000263080>{number = 7, name = (null)}____

5. 其他方法介绍:

NSOperation方法:

BOOL cancelled;//判断任务是否取消
BOOL executing; //判断任务是否正在执行
BOOL finished; //判断任务是否完成
BOOL concurrent;//判断任务是否是并发
NSOperationQueuePriority queuePriority;//修改 Operation 执行任务线程的优先级
void (^completionBlock)(void) //用来设置完成后需要执行的操作
- (void)cancel; //取消任务
- (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕

NSOperation Queue方法:

NSUInteger operationCount; //获取队列的任务数
- (void)cancelAllOperations; //取消队列中所有的任务
- (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕
[queue setSuspended:YES]; // 暂停queue
[queue setSuspended:NO]; // 继续queue

补充知识点:

6. 并发的NSOperation

在上面创建自定义子类NSOperation任务的时候只是创建了串行的NSOperation子类,只要重写main方法即可。现在我们就来看看如何实现并发的子类NSOperation。

NSOperation有三个状态量isCancelled, isExecutingisFinished.

实现并发(concurrent)的NSOperation步骤:

  1. 重写start()函数
  1. 重写main函数
  1. isExecuting 和 isFinished
  1. 重写isConcurrent函数

并发的NSOperationDemo:

JYConcurrentOperation2.h

#import <Foundation/Foundation.h>

typedef void (^JYCompletionBlock)(NSData *imageData);

@interface JYConcurrentOperation2 : NSOperation

@property (nonatomic, copy) JYCompletionBlock comBlock;

@end

JYConcurrentOperation2.m

#import "JYConcurrentOperation2.h"

@interface JYConcurrentOperation2 ()<NSURLConnectionDelegate,NSURLConnectionDataDelegate>

@property (nonatomic, strong) NSURLConnection *connection;
@property (nonatomic, strong) NSMutableData *data;

@property (nonatomic, assign) CFRunLoopRef operationRunLoop;

@end

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

- (BOOL)isConcurrent{
    return YES;
}

- (void)start{
    
    if (self.isCancelled) {
        [self finish];
    }
    
    [self willChangeValueForKey:@"isExecuting"];
    _executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
    
    
    NSURL *url=[NSURL URLWithString:@"http://p1.bpimg.com/524586/79a7a2915b550222.jpg"];
    NSURLRequest * request = [NSURLRequest requestWithURL:url];
    _connection = [[NSURLConnection alloc] initWithRequest:request
                                                  delegate:self];
    
    /*
     if (![NSThread isMainThread])
     {
     [self performSelectorOnMainThread:@selector(start)
     withObject:nil
     waitUntilDone:NO];
     return;
     }
     // set up NSURLConnection...
     or
     
     [[NSOperationQueue mainQueue] addOperationWithBlock:^{
     self.connection = [NSURLConnection connectionWithRequest:self.request delegate:self];
     }];
     */
    
    NSOperationQueue *currentQueue = [NSOperationQueue currentQueue];
    BOOL backgroundQueue  = (currentQueue != nil && currentQueue != [NSOperationQueue mainQueue]);
    NSRunLoop *targetRunLoop = (backgroundQueue)?[NSRunLoop currentRunLoop]:[NSRunLoop mainRunLoop];
    
    [self.connection scheduleInRunLoop:targetRunLoop forMode:NSRunLoopCommonModes];
    [self.connection start];
    
    // make NSRunLoop stick around until operation is finished
    if (backgroundQueue) {
        self.operationRunLoop = CFRunLoopGetCurrent(); CFRunLoopRun();
    }
}

- (void)cancel{
    if (!_executing) return;
    
    [super cancel];
    [self finish];
}

- (void)finish{
    
    self.connection = nil;
    
    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];
    
    _executing = NO;
    _finished = YES;
    
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
    
    if (self.comBlock) {
        self.comBlock (_data);
    }
}

#pragma mark - NSURLConnection delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // to do something...
    self.data       = [NSMutableData data];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    // to do something...
    NSLog(@"%ld",data.length);
    [_data appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    if (self.operationRunLoop) {
       CFRunLoopStop(self.operationRunLoop);
    }
     if (self.isCancelled) return;
    
    [self finish];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [self finish];
}



@end

并发的使用:

- (void)studyNSOperation4{
    //子队列运行
    JYConcurrentOperation2 *op = [[JYConcurrentOperation2 alloc] init];

    op.comBlock = ^(NSData *data){
        UIImage *image = [UIImage imageWithData:data];
        self.imageView.image = image;
    };
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:op];
    
}

并发NSOperation的Demo一些解释:

代码中引入了RunLoop的东西->原因呢:

结尾:

今天的NSOperation就介绍到这里,里面有不对的地方希望大神们可以提出来,今天提到了RunLoop,大家可以学习一下相关的知识点。同时多线程的NSThread、GCD、NSOperation在这三篇文章中基本上介绍完了。

如果你喜欢请点喜欢,加关注哦_


ThreadDemo下载链接

iOS开发之多线程编程总结(一)
iOS开发之多线程编程总结(二)

参考资料:
http://www.cocoachina.com/ios/20150807/12911.html
http://www.jianshu.com/p/ebb3e42049fd

上一篇下一篇

猜你喜欢

热点阅读