天天看點

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