天天看点

NSOperation的使用一种加入NSOperationQueue:另一种直接调用NSOperation,[operation start]

一种加入NSOperationQueue:

分为重写main和重写start,重写main不用关心operation对象的释放问题,不要处理任务状态

重写start方法,需要在start方法或者main方法中对finished赋值为yes,operation对象才会释放,并且需要主动调用main方法

另一种直接调用NSOperation,[operation start]

为了防止外部多次调用[operation start]方法,需要重写start方法在里面加上判断阻止多次执行start方法

if(_finished){ 
 return;
}

if(self.isExecuting){
 return;
}
           

NSOperation默认是非并发的:

NSOperation调用start方法即可开始执行操作,NSOperation对象默认按同步方式执行,也就是在调用start方法的那个线程中直接执行。NSOperation对象的isConcurrent方法会告诉我们这个操作相对于调用start方法的线程,是同步还是异步执行。isConcurrent方法默认返回NO,表示操作与调用线程同步执行

那肿么实现并发(concurrent)的NSOperation呢? 也很简单:

1). 重写isConcurrent函数, 返回YES, 这个告诉系统各单位注意了我这个operation是要并发的.

2). 重写start()函数.

3). 重写isExecuting和isFinished函数

为什么在并发情况下需要自己来设定isExecuting和isFinished这两个状态量呢? 因为在并发情况下系统不知道operation什么时候finished, operation里面的task一般来说是异步执行的, 也就是start函数返回了operation不一定就是finish了, 这个你自己来控制, 你什么时候将isFinished置为YES(发送相应的KVO消息), operation就什么时候完成了. Got it? Good.

详见https://www.jianshu.com/p/ebb3e42049fd这篇博客,写得很好

如果你是在主线程调用的这个并发的operation, 那一切都是非常的perfect, 就算你当前在操作UI也不影响operation的下载操作. BUT, 如果你是在子线程调用的, 或者把operation加到了非main queue, 那么问题来了, 你会发现这货的NSURLConnection delegate不走了,

Option 1是让start函数在主线程运行(即使[operation start]是在子线程调用的).

Option 2是让operation的start函数在子线程运行, 但是我们为它创建一个RunLoop. 然后把URL connection schedule。

这个Option2也是AF2.x的做法,具体如下:

AF的start方法中

[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
           

networkRequestThread这个方法里面:

+ (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)networkRequestThreadEntryPoint:(id)__unused object {

@autoreleasepool {

[[NSThread currentThread] setName:@"AFNetworking"];

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

[runLoop run];

}

}
           

创建了一个单例子线程,然后在子线程中开启runloop保活这个子线程

然后会执行operationDidStart方法,这个方法里面是

- (void)operationDidStart {

[self.lock lock];

if (![self isCancelled]) {

self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

for (NSString *runLoopMode in self.runLoopModes) {

[self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];

[self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];

}

[self.outputStream open];

[self.connection start];

}

[self.lock unlock];

dispatch_async(dispatch_get_main_queue(), ^{

[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];

});

}
           

其中

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

for (NSString *runLoopMode in self.runLoopModes) {

[self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];

[self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];

}

afn中写的很清楚先通过 [NSRunLoop currentRunLoop]获取当前的runloop,然后把urlconnection的网络回调加入到runLoop中

如果currentrunloop是mainrunloop那urlconnection的delegate回调自然是在主线程中,currentrunloop是刚刚创建的子线程(这个子线程只有上面刚刚创建的子线程一种可能性,因为self performSelector:@selector(operationDidStart) onThread这个方法指定线程了)的队列,那么回调自然在上面刚刚创建的子线程中。

子类化NSOperation

注意点:1,重写main方法和start方法区别

重写start方法,在start方法中可以做判断如果isfinished就return,防止多次调用,而且里面需要主动调用main方法

而重写main方法需要根据任务的执行状态去设置

2,队列中调用cancel而进入队列的nsoperation一定要等当前operation执行完,然后暂停其他的operation。

NSBlockoperation

1,通过operation来直接调用main方法和start方法,main会多次调用,而系统在start方法中对blockoperation的状态isfinished状态进行判断,如果isfinished=yes,就不会执行了

2,通过kvo监听blockoperation,如果blockoperaion执行完了就回调,如下:(一个operation里面有几个任务,addExecutionBlock添加的任务是在子线程的):

    blockOperation = [[NSBlockOperation alloc] init];

    [blockOperation addObserver:self forKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew context:nil];

    [blockOperation addExecutionBlock:^{

        NSLog(@"one block:%@", [NSThread currentThread]);

    }];

    [blockOperation addExecutionBlock:^{

        NSLog(@"two block:%@", [NSThread currentThread]);

    }];

    [blockOperation addExecutionBlock:tblock];

    [blockOperation start];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{

    NSLog(@"%@", keyPath);

    NSLog(@"%@", change);

}

NSInvocationoperation

可以通过nsinvocationoperation调用多个参数的方法