一种加入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调用多个参数的方法