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
子类
NSInvocationOperation
和NSBlockOperation
都是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之后可以退出