NSOperation NSOperationQueue 父类是 NSObject
1. 使用NSOperation进行多线程的开发的大概思路
NSOperation和NSOperationQueue实现多线程的具体步骤:
(1)先将需要执行的操作封装到一个NSOperation对象
(2)然后将NSOperation对象添加到NSOperationQueue中
(3)系统会⾃动将NSOperationQueue中的NSOperation取出来
(4)将取出的NSOperation封装的操作放到⼀条新线程中执⾏
NSOperation是一个抽象的基类,表示一个独立的计算单元,可以为子类提供有用且线程安全的建立状态,优先级,依赖和取消等操作。但是它不具备封装的功能,我们可以使用它的子类。
(1)NSInvocationOperation 只有加入到queue中才执行异步操作
(2)NSBlockOperation 只要封装的次数超过了1次就可以执行异步操作了
(3)自定义类继承NSOperation 实现内部相应的⽅法
NSOperation
- (void)start;
在当前任务状态和依赖关系合适的情况下,启动NSOperation的main方法任务,需要注意缺省实现只是在当前线程运行。如果需要并发执行,子类必须重写这个方法,并且使 - (BOOL)isConcurrent 方法返回YES
- (void)main;
定义NSOperation的主要任务代码
//状态的变化
@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;
当前任务状态是否已标记为取消
@property (readonly, getter=isExecuting) BOOL executing;
NSOperation任务是否在运行
@property (readonly, getter=isFinished) BOOL finished;
NSOperation任务是否已结束
@property (readonly, getter=isReady) BOOL ready;
是否能准备运行,这个值和任务的依赖关系相关
//同步异步
@property (readonly, getter=isConcurrent) BOOL concurrent;
// To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);
- (void)addDependency:(NSOperation *)op;
加上任务的依赖,也就是说依赖的任务都完成后,才能执行当前任务
- (void)removeDependency:(NSOperation *)op;
取消任务的依赖,依赖的任务关系不会自动消除,必须调用该方法
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
得到所有依赖的NSOperation任务
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
@property NSOperationQueuePriority queuePriority;
队列的优先级
@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);
- (void)setCompletionBlock:(void (^)(void))block; //设置NSOperation结束后运行的block代码,由于NSOperation有可能被取消,所以这个block运行的代码应该和NSOperation的核心任务无关。
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);
/阻塞当前线程,直到该NSOperation结束。可用于线程执行顺序的同步
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);
NSOperation 提供了四种状态,ready,cancelled,executing,finished。因此我们在开发中也必须处理其中的状态。这些状态的通知,是通过KeyPath的KVO通知决定的。如果我们需要手动改变自己关心的状态时,要自己手动发送通知。每个属性都是独立的,同时只可能有一个状态是YES。
依赖:
1.当某个NSOperation对象依赖于其它NSOperation对象的完成时,就可以通过addDependency方法添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前NSOperation对象才会开始执行操作。另外,通过removeDependency方法来删除依赖对象。
-(void)test5{
NSInvocationOperation * operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test5Run) object:nil];
NSInvocationOperation * operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation2Run) object:nil];
[operation1 addDependency:operation2];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperation:operation1];
[queue addOperation:operation2];
}
- (void)test5Run{
NSLog(@"operation1--%@",[NSThread currentThread]);
}
- (void)operation2Run{
NSLog(@"operation2---%@",[NSThread currentThread]);
}
/*
当注视掉依赖[operation1 addDependency:operation2];的代码,会开启两条线程,但是执行的顺序是不确定的。
2016-03-25 14:46:16.662 NSOperationDemo1[9972:153185] operation1--<NSThread: 0x7fec51e0add0>{number = 2, name = (null)}
2016-03-25 14:46:16.662 NSOperationDemo1[9972:153186] operation2---<NSThread: 0x7fec51d138c0>{number = 3, name = (null)}
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
2016-03-25 14:47:23.240 NSOperationDemo1[10009:154293] operation2---<NSThread: 0x7fa13041b5f0>{number = 3, name = (null)}
2016-03-25 14:47:23.240 NSOperationDemo1[10009:154292] operation1--<NSThread: 0x7fa130728210>{number = 2, name = (null)}
当打开了依赖的代码后,执行的顺序确定了,但是只会开启一条线程。
2016-03-25 14:48:01.782 NSOperationDemo1[10035:154829] operation2---<NSThread: 0x7fb162e1e800>{number = 2, name = (null)}
2016-03-25 14:48:01.784 NSOperationDemo1[10035:154829] operation1--<NSThread: 0x7fb162e1e800>{number = 2, name = (null)}
*/
对于添加到queue中的operations,它们的执行顺序取决于2点:
1.首先看看NSOperation是否已经准备好:是否准备好由对象的依赖关系确定
2.然后再根据所有NSOperation的相对优先级来确定。优先级等级则是operation对象本身的一个属性。默认所有operation都拥有“普通”优先级,不过可以通过setQueuePriority:方法来提升或降低operation对象的优先级。优先级只能应用于相同queue中的operations。如果应用有多个operation queue,每个queue的优先级等级是互相独立的。因此不同queue中的低优先级操作仍然可能比高优先级操作更早执行。
注意:优先级不能替代依赖关系,优先级只是对已经准备好的 operations确定执行顺序。先满足依赖关系,然后再根据优先级从所有准备好的操作中选择优先级最高的那个执行。
执行
执行一个operation有两种方法,
(1)第一种是自己手动的调用start这个方法,这种方法调用会在当前调用的线程进行同步执行,所以在主线程里面自己一定要小心的调用,不然就会把主线程给卡死。
(2)第二种是将operation添加到operationQueue中去,这个也是我们用得最多的也是提倡的方法。NSOperationQueue会在我们添加进去operation的时候尽快进行执行。当然如果NSOperationQueue的maxConcurrentOperationCount如果设置为1的话,进相当于FIFO了。
队列是怎么调用我们的执行的操作的呢?自定义NSOperation
(1)如果你只是想弄一个同步的方法,那很简单,你只要重写main这个函数,在里面添加你要的操作。
(2)如果想定义异步的方法的话就重写start方法。在你添加进operationQueue中的时候系统将自动调用你这个start方法,这时将不再调用main里面的方法。
取消
NSOperation允许我们调用-(void)cancel取消一个操作的执行。这个取消的步骤是这样的,
(1)如果这个操作在队列中没有执行,那么这个时候取消并将状态finished设置为YES,那么这个时候的取消就是直接取消了。
(2)如果这个操作已经在执行了,那么我们只能等其操作完成。当我们调用cancel方法的时候,他只是将isCancelled设置为YES。
所以,在我们的操作中,我们应该在每个操作开始前,或者在每个有意义的实际操作完成后,先检查下这个属性是不是已经设置为YES。如果是YES,则后面操作都可以不用在执行了。
在网上查看到的的简单实例代码
最后我们看看一个简单的小示例,在.m文件里面我们将重写finished executing两个属性。我们重写set方法,手动发送keyPath的KVO通知。在start函数中,我们首先判断是否已经取消,如果取消的话,我们将直接return,并将finished设置为YES。如果没有取消操作,我们将_executing设置为YES,表示当前operation正在执行,继续执行我们的逻辑代码。在执行完我们的代码后,别忘了设置operation的状态,将_executing设置为NO,并将finished设置为YES,这样我们就已经很简单的完成了我们的多线程操作任务。
@interface TestOperation ()
@property (nonatomic, assign) BOOL finished;
@property (nonatomic, assign) BOOL executing;
@end
@implementation TestOperation
@synthesize finished = _finished;
@synthesize executing = _executing;
- (void)start
{
if ([self isCancelled]) {
_finished = YES;
return;
} else {
_executing = YES;
//start your task;
//end your task
_executing = NO;
_finished = YES;
}
}
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
2. NSInvocationOperation是NSOperation的子类。NSInvocationOperation提供了一套简单的多线程编程方案。
- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
- (instancetype)initWithInvocation:(NSInvocation *)inv NS_DESIGNATED_INITIALIZER;
调用父类的start方法启动任务
-(void)test1{
NSInvocationOperation * operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationRun) object:nil];
[operation start];
}
- (void)invocationRun{
NSLog(@"number----%@",[NSThread currentThread]);
}
/*
这里表示是在主线程中执行的,并没有开启新的线程
2016-03-23 17:07:13.810 NSOperationDemo1[12996:258600] number----<NSThread: 0x7fa978c07c60>{number = 1, name = main}
*/
-(void)test2{
NSInvocationOperation * operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationRun) object:nil];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperation:operation];
NSLog(@"done---%@",[NSThread currentThread]);
}
/*
把NSInvocationOperation创建的任务只有加入到NSOperationQueue中,就可以开辟新的线程了。
2016-03-23 17:20:45.111 NSOperationDemo1[13342:267547] done---<NSThread: 0x7fcf6b402970>{number = 1, name = main}
2016-03-23 17:20:45.111 NSOperationDemo1[13342:267631] number----<NSThread: 0x7fcf6b7062f0>{number = 2, name = (null)}
*/
NSBlockOperation
NSOperation的子类
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;创建方法
- (void)addExecutionBlock:(void (^)(void))block;添加任务
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;
-(void)test3{
NSBlockOperation * operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"我要执行的任务内容---%@",[NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"添加了我的operation---%@",[NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"第二次添加operation----%@",[NSThread currentThread]);
}];
[operation start];
}
/*
只要NSBlockOperation封装的操作超过了一次,就会异步执行操作这里也要注意
2016-03-23 17:40:39.591 NSOperationDemo1[13874:282401] 我要执行的任务内容---<NSThread: 0x7fd2c9c092c0>{number = 1, name = main}
2016-03-23 17:40:39.591 NSOperationDemo1[13874:282482] 第二次添加operation----<NSThread: 0x7fd2c9d34e00>{number = 2, name = (null)}
2016-03-23 17:40:39.591 NSOperationDemo1[13874:282481] 添加了我的operation---<NSThread: 0x7fd2c9f04b60>{number = 3, name = (null)}
*/
NSOperationQueue
NSOperationQueue的作⽤:
(1)NSOperation可以调⽤start⽅法来执⾏任务,但默认是同步执行的
(2)如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
添加操作到NSOperationQueue中,自动执行操作,自动开启线程
- (void)addOperation:(NSOperation *)op;
加入到执行队列中,如果isReady则开始执行
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);
批量加入执行operation,wait标志是否当前线程等到所有operation结束
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);
相当于加入一个NSBlockOperation执行任务
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
返回已加入执行operation的数组,当某个operation结束后会自动从这个数组清除
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
返回已加入执行operation的数目
@property NSInteger maxConcurrentOperationCount;
设置最大并发执行数,如果为1则同时只有一个并发任务在运行,可控制顺序执行关系
@property (getter=isSuspended) BOOL suspended;
是否暂停将要执行的operation,但不会暂停已开始的operation
@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);
- (void)cancelAllOperations;
取消所有operation的执行,实质是调用各个operation的cancel方法
- (void)waitUntilAllOperationsAreFinished;
当前线程等待,直到opA和opB都执行结束
如果要求opB在opA执行完成后才开始执行,需要加上依赖关系即可:
+ (nullable NSOperationQueue *)currentQueue NS_AVAILABLE(10_6, 4_0);
返回当前NSOperationQueue,如果当前线程不是在NSOperationQueue上运行则返回nil
+ (NSOperationQueue *)mainQueue NS_AVAILABLE(10_6, 4_0);
返回主线程的NSOperationQueue,缺省总是有一个queue
-(void)test4{
NSInvocationOperation * operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testOne) object:nil];
NSInvocationOperation * operaton2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testTwo) object:nil];
NSBlockOperation * operatino3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation3---%@",[NSThread currentThread]);
}];
[operatino3 addExecutionBlock:^{
NSLog(@"operation4----%@",[NSThread currentThread]);
}];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperation:operation1];
[queue addOperation:operaton2];
[queue addOperation:operatino3];
}
- (void)testOne{
NSLog(@"operation1---%@",[NSThread currentThread]);
}
- (void)testTwo{
NSLog(@"operation2---%@",[NSThread currentThread]);
}
/*
2016-03-23 17:58:14.721 NSOperationDemo1[14518:296223] operation2---<NSThread: 0x7fda914240b0>{number = 3, name = (null)}
2016-03-23 17:58:14.721 NSOperationDemo1[14518:296270] operation3---<NSThread: 0x7fda91508b90>{number = 4, name = (null)}
2016-03-23 17:58:14.720 NSOperationDemo1[14518:296225] operation1---<NSThread: 0x7fda91413420>{number = 2, name = (null)}
2016-03-23 17:58:14.721 NSOperationDemo1[14518:296224] operation4----<NSThread: 0x7fda914119d0>{number = 5, name = (null)}
*/
系统自动将NSOperationqueue中的NSOperation对象取出,将其封装的操作放到一条新的线程中执行。上面的代码示例中,一共有四个任务,operation1和operation2分别有一个任务,operation3有两个任务。一共四个任务,开启了四条线程。通过任务执行的时间全部都是721可以看出,这些任务是并行执行的
Demo下载图片
- (void)createArray{
self.mutableArray = [[NSMutableArray alloc]init];
NSString * str = @"http://h.hiphotos.baidu.com/baike/w%3D268/sign=30b3fb747b310a55c424d9f28f444387/1e30e924b899a9018b8d3ab11f950a7b0308f5f9.jpg";
for(int i=0;i<80;i++){
[self.mutableArray addObject:str];
}
}
-(void)createTableView{
self.tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, [[UIScreen mainScreen]bounds].size.width, [[UIScreen mainScreen]bounds].size.height) style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.view addSubview:self.tableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.mutableArray.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString * cellID = @"ModelCell";
ModelCell * cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if(cell == nil){
cell = [ModelCell loadFromXib];
}
cell.testLabel.text = [self.mutableArray objectAtIndex:indexPath.row];
//开启一个线程去下载图片
NSInvocationOperation * operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downloadImage:) object:cell];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperation:operation];
return cell;
}
- (void)downloadImage:(ModelCell *)cell{
NSIndexPath * index = [self.tableView indexPathForCell:cell];
NSURL *url=[NSURL URLWithString:[self.mutableArray objectAtIndex:index.row]];
NSData *data=[NSData dataWithContentsOfURL:url];
UIImage *imgae=[UIImage imageWithData:data];
//下载完成在主线程赋值
dispatch_async(dispatch_get_main_queue(), ^{
cell.downImage.image=imgae;
});
}