一種加入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調用多個參數的方法