NSOperation
iOS4推出GCD后,NSOPeration和NSOperationQueue是基于GCD更高一层的封装。所以NSOperation完全地面向对象。比GCD更简单易用、代码可读性也更高。
NSOperation和NSOperationQueue分别对应GCD的任务和队列,NSOperation对比GCD会带来一点额外的系统开销,但是Operation操作性更好
NSOperation作为一个基类,不能直接创建任务,需要通过他的子类NSInvocationOperation
、NSBlockOperation
创建任务;而NSOperationQueue
是用来管理任务的队列
@interface NSOperation : NSObject
@interface NSInvocationOperation : NSOperation
@interface NSBlockOperation : NSOperation
//管理任务的队列
@interface NSOperationQueue : NSObject
还可以通过KVO监听操作对象的各种状态,还可以添加依赖,设置优先级等等
@property (readonly, getter=isCancelled) BOOL cancelled;
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isReady) BOOL ready; //任务想要执行ready状态必须为YES
设置优先级: 异步执行的时候,优先级高的先执行
添加依赖: addDependency:
B依赖A,需要等到A执行完了,B进入ready
状态,再执行B,
如下代码:[bo addDependency:ao];
bo 依赖于ao,如果不添加到队列,那么需要自己手动start
,并且 ao要先 start
,如果bo先start
那么,会崩溃,因为bo没有进入ready
状态就开始start
- (void)test {
NSInvocationOperation *ao = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testio:) object:@{@"key":@"value"}];
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"bb---%@",[NSThread currentThread]);
}];
[bo addDependency:ao];
[ao start];
[bo start];
}
1:NSInvocationOperation
NSInvocationOperation
没有额外添加任务的方法,使用NSInvocationOperation创建的对象只会有一个任务。而且start方法是在当前线程中执行,阻塞当前线程
,只有等到任务结束后,才会往下执行
NSInvocationOperation *io = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationOperation) object:nil];
io.queuePriority = NSOperationQueuePriorityVeryLow;
[io start];
2:NSBlockOperation
NSBlockOperation
通过blockOperationWithBlock:
创建对象,在创建的时候也添加一个任务。如果想添加更多的任务,可以使用addExecutionBlock:
方法。也可以通过init
创建
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1111-%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"3333-%@",[NSThread currentThread]);
}];
[blockOperation start];
注意:
当只有一个任务时,NSBlockOperation
不会开辟新线程,直接在当前线程同步执行任务。并且 阻塞当前线程
,
任务数大于1时,第一个任务,不会开辟线程,直接在当前线程同步执行任务(同上)。而通过addExecutionBlock:
方法添加的其他任务会开辟新线程,异步执行任务。(前提是没有添加到NSOperationQueue
中,添加到NSOperationQueue
的任务默认都是异步执行,全部开启新的子线程)
二、NSOperationQueue
NSOperationQueue
:相当于CGD中的队列,而且是并行队列
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSLog(@"1111-%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"2222-%@",[NSThread currentThread]);
}];
可以通过[queue addOperations:@[operation0,operation1] waitUntilFinished:YES];
达到异步下载完成后,再执行剩下的某个任务,相当于CGD中的dispatch_barrier_async
方法。
NSOperationQueue的属性控制
setMaxConcurrentOperationCount:
方法可以设置队列的最大并发数cancel
方法取消单个操作对象。cancelAllOperations
方法取消队列中全部操作(针对未执行的任务),当我们调用cancel方法的时候,他只是将isCancelled设置为YES- 暂停
suspended
:当任务的suspended
设置为YES,队列会停止对任务调度(针对未执行的任务)。如果任务正在执行将不会受到影响,因为任务已经被队列调度到一个线程上并执行。
总结:GCD的核心是C语言写的系统服务,执行和操作简单高效,因此NSOperation底层也通过GCD实现,换个说法就是NSOperation是对GCD更高层次的抽象,这是他们之间最本质的区别
所以NSOperation是对GCD的区别如下:
-
依赖关系,NSOperation可以设置两个NSOperation之间的依赖,第二个任务依赖于第一个任务完成执行,GCD无法设置依赖关系,不过可以通过dispatch_barrier_async来实现这种效果;
-
KVO(键值对观察),NSOperation和容易判断Operation当前的状态(是否执行,是否取消),对此GCD无法通过KVO进行判断;
-
优先级,NSOperation可以设置自身的优先级,但是优先级高的不一定先执行,GCD只能设置队列的优先级,无法在执行的block设置优先级;
-
继承,NSOperation是一个抽象类实际开发中常用的两个类是NSInvocationOperation和NSBlockOperation,同样我们可以自定义NSOperation,GCD执行任务可以自由组装,没有继承那么高的代码复用度;
-
效率,直接使用GCD效率确实会更高效,NSOperation会多一点开销,但是通过NSOperation可以获得依赖,优先级,继承,键值对观察这些优势,相对于多的那么一点开销确实很划算
NSOperation和NSOperationQueue的注意点
- 一个NSOperation的任务执行了start后,不能添加到
NSOperationQueue
,因为他已经执行了,状态已经finished。还要添加到队列会crash,-[NSOperationQueue addOperation:]: operation is finished and cannot be enqueued'
- 添加依赖:
[bo addDependency:ao];
bo 依赖于ao,如果不添加到队列,那么需要自己手动start
,并且 ao要先start
,如果bo先start
那么,会崩溃,因为bo没有进入ready
状态就开始start
,如果添加到队列就不用我们手动start
,不用管,队列会去调度
三、多线程间的通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 1.子线程下载图片
//code ...
// 2.回到主线程设置图片
dispatch_async(dispatch_get_main_queue(), ^{
//code ...
});
});
}
四、dispatch也是可以cancel的
已经执行的任务不能cancel。
只能取消未执行的任务,dispatch_block_cancel()
和dispatch_source_cancel()
,就能取消未执行的任务。注意 取消的必须是dispatch_block_t
类型,(就是通过dispatch_block_create
创建的任务),下面的b1就不能放在dispatch_block_cancel(b1);
里面,放进去就crash,因为b1不是dispatch_block_create
创建的。
dispatch_block_t
dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block); //flags 标记,后面某个时机使用
- (void)viewDidLoad {
dispatch_block_t b1 = ^{
for (int i = 0; i < 5; i ++) {
sleep(1);
NSLog(@"bb11---%@",[NSThread currentThread]);
}
};
dispatch_block_t b2 = dispatch_block_create(0, ^{
for (int i = 0; i < 5; i ++) {
NSLog(@"bb22---%@",[NSThread currentThread]);
}
});
// dispatch_block_t b2 = ^{};
dispatch_queue_t qu = dispatch_queue_create("com.ob", DISPATCH_QUEUE_SERIAL);
dispatch_async(qu,b1);
dispatch_async(qu,b2);
dispatch_block_cancel(b2);
dispatch_source_cancel(dispatch_source_t _Nonnull source)
}
dispatch_get_global_queue
dispatch_get_global_queue
会获取一个全局队列,我们姑且理解为系统为我们开启的一些全局线程。我们用priority指定队列的优先级,而flag作为保留字段备用(一般为0)。
全局队列可以设置优先级,但是任务不能设置优先级
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t aHQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t aLQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
五、异步转同步
异步转同步:本质就是让同步任务等待异步执行完毕,再回到同步任务,所以有很多实现:dispatch_group_t
,dispatch_semaphore_t
和锁lock
都可以实现,
- (id)sync_to_async:(NSString *)obj {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("ob", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"一个同步任务开始");
__block id value;
NSLock *lock = [[NSLock alloc] init];
[lock lock];
// dispatch_group_enter(group);
dispatch_async(queue, ^{
sleep(5);
value = @YES;
NSLog(@"执行异步任务 %@", @YES);
// dispatch_group_leave(group);
[lock unlock];
});
[lock lock];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"等待异步执行完,回到同步任务,其实就是在这里等待: %@", value);
[lock unlock];
return value;
}