NSOperation筆記

NSOperation

NSOperation表示了一個(gè)獨(dú)立的任務(wù)。NSOperation是一個(gè)抽象類。不能直接使用。你可以使用系統(tǒng)定義好的它的兩個(gè)字類NSInvocationOperationNSBlockOperation。也可以自己定義一個(gè)繼承自NSOperation的類使用。
很多執(zhí)行任務(wù)類型的案例都很好的運(yùn)用了NSOperation,包括AFNetworking,SDWebIamge,圖像壓縮,自然語(yǔ)言處理或者其他很多需要返回處理后數(shù)據(jù)的、可重復(fù)的、結(jié)構(gòu)化的、相對(duì)長(zhǎng)時(shí)間運(yùn)行的任務(wù)。
NSOperationQueue控制著NSOperation任務(wù)的執(zhí)行。通常是吧一個(gè)NSOperation對(duì)象添加到NSOperationQueue中,NSOperationQueue會(huì)直接在子線程里執(zhí)行添加到其中的NSOperation任務(wù)。
如果不使用NSOperationQueue,也可以調(diào)用NSOperation的start方法來(lái)執(zhí)行任務(wù)。
直接通過調(diào)用 start 方法來(lái)執(zhí)行一個(gè) operation ,但是這種方式并不能保證 operation 是異步執(zhí)行的。

isAsynchronous

NSOperation 類的 isAsynchronous 方法的返回值標(biāo)識(shí)了一個(gè) operation 相對(duì)于調(diào)用它的 start 方法的線程來(lái)說(shuō)是否是異步執(zhí)行的。在默認(rèn)情況下,isAsynchronous 方法的返回值是 NO ,也就是說(shuō)會(huì)阻塞調(diào)用它的 start 方法的線程。
如果想要調(diào)用它的 start方法后異步執(zhí)行任務(wù),需要編寫一些額外的代碼來(lái)讓這個(gè) operation 異步執(zhí)行。僅僅重寫isAsynchronous返回YES是不會(huì)讓任務(wù)異步執(zhí)行的,還需要自己創(chuàng)建新的線程來(lái)保證任務(wù)的異步執(zhí)行。更多信息,可以看Asynchronous Versus Synchronous Operations
系統(tǒng)提供的NSInvocationOperationNSBlockOperation這兩個(gè)類的isAsynchronous都是NO,也就是這兩個(gè)類都會(huì)阻塞調(diào)用start方法的線程。

Asynchronous Versus Synchronous Operations

如果使用start方法來(lái)開啟一個(gè)任務(wù),默認(rèn)是同步的。只需要在子類里重寫main方法,在這個(gè)方法里寫上你需要執(zhí)行的任務(wù)。
如果要用start方法開啟一個(gè)異步地任務(wù),則至少需要重寫下面的方法:

  • start:start方法會(huì)開始執(zhí)行NSOperation的任務(wù),這個(gè)方法的實(shí)現(xiàn)會(huì)更新isExecuting的狀態(tài)并且會(huì)調(diào)用NSOperation的main方法。如果NSOperation已經(jīng)取消了或者已經(jīng)完成了,這個(gè)方法會(huì)直接返回,不會(huì)調(diào)用面方法。如果這個(gè)NSOperation正在執(zhí)行或者還沒有準(zhǔn)備好執(zhí)行,這個(gè)方法會(huì)拋出一個(gè)NSInvalidArgumentException異常。
    如果這個(gè)NSOperation依賴的其他NSOperation尚未完成,則這個(gè)NSOperation的isReady是NO。重寫這個(gè)方法不能調(diào)用super。這個(gè)方法里還需要追蹤NSOperation的狀態(tài)。它應(yīng)分別手動(dòng)地為isExecuting和isFinished的keypath生成KVO通知。手動(dòng)的生成KVO通知
    NSOperation已經(jīng)添加到NSOperationQueue后在調(diào)用這個(gè)方法或者先調(diào)用了這個(gè)方法然后再把NSOperation添加到NSOperationQueue都是錯(cuò)誤的。
    在并發(fā)操作中,stat方法負(fù)責(zé)以異步地方式開啟NSOperation。無(wú)論是創(chuàng)建新的線程還是使用異步函數(shù),都要在start方法里完成。
    在開始NSOperation時(shí),start方法里應(yīng)該更新isExecuting這個(gè)屬性來(lái)報(bào)告NSOperation的狀態(tài),可以通過手動(dòng)發(fā)一個(gè)isExecuting key path KVO通知。
    當(dāng)完成或取消NSOperation時(shí),需要對(duì)isExecuting和isFinished手動(dòng)生成KVO通知。在取消NSOperation的情況下,即使NSOperation沒有完成任務(wù),人需要更新isFinished。因?yàn)樵陉?duì)列中的NSOperation的狀態(tài)完成的情況下才會(huì)被移除。
    注意:
    在start的方法里任何時(shí)候都不應(yīng)該調(diào)用super。在定義并發(fā)的NSOperation時(shí),需要提供默認(rèn)的start方法提供的行為,其中包括開啟任務(wù)和生成KVO通知。start方法話還應(yīng)該見檢查在任務(wù)開啟前NSOperation是否已經(jīng)被取消了。Cancel Command

  • isAsynchronous:這個(gè)只默認(rèn)是NO,這個(gè)值本身不會(huì)影響任務(wù)同步還是異步。

  • isExecuting:自定義并發(fā)的NSOperation時(shí),必須要重寫這個(gè)屬性。當(dāng)你改變這個(gè)屬性時(shí),需要手動(dòng)生成kvo通知。

  • isFinished:自定義并發(fā)的NSOperation時(shí),必須要重寫這個(gè)屬性。當(dāng)你改變這個(gè)屬性時(shí),需要手動(dòng)生成kvo通知。

NSOperation State

和NSOperation的狀態(tài)關(guān)聯(lián)的屬性:

  • isReady:isReady表示NSOperation什么時(shí)候準(zhǔn)備好執(zhí)行。它的值是YES表示已經(jīng)準(zhǔn)備好執(zhí)行,如果NSOperation有其他依賴的NSOperation還沒有完成,則isReady的值是NO。
    大部分情況下,你不需要自己管理這個(gè)屬性,如果你自定義的operation的準(zhǔn)備狀態(tài)是由依賴操作意外的因素決定的,你可以自己實(shí)現(xiàn)這個(gè)屬性。
  • isExecuting:這個(gè)屬性表示NSOperation是否正在執(zhí)行任務(wù)。operation正在執(zhí)行任務(wù)時(shí)是YES,否則是NO。
  • isFinished:這個(gè)值是YES表示operation的任務(wù)完成了或者operation被取消了。當(dāng)這個(gè)值是YES是,opearation queue才會(huì)把operation移除隊(duì)列,operation的依賴關(guān)系也會(huì)被清除。
  • isCancelled:這個(gè)屬性默認(rèn)值是NO,調(diào)用cancel方法來(lái)設(shè)置這個(gè)值是YES。一旦operation取消了,operation的狀態(tài)要設(shè)置為完成。取消operation并不會(huì)阻止operation執(zhí)行代碼。如果方法返回YES,operation需要定期調(diào)用此方法并停止。
    在完成任務(wù)之前,你應(yīng)該始終檢查這個(gè)屬性,通常在自定義的main方法開始時(shí)檢查。

cancel operation

cancel方法調(diào)用后會(huì)把isCancelled設(shè)置為YES。這個(gè)方法不會(huì)強(qiáng)制停止operation的代碼,它僅僅會(huì)更新operation內(nèi)部的標(biāo)志來(lái)反映狀態(tài)的變化。如果operation已經(jīng)完成執(zhí)行了,這個(gè)方法不起作用。在operaton queue里的還未開始執(zhí)行任務(wù)的operation調(diào)用cancel方法后,可以比平時(shí)更快的把operation從operation queue里移除。如果取消不在opeation queue中的operation,則此方法會(huì)立即將operation標(biāo)記為完成狀態(tài)。
NSOperation的默認(rèn)實(shí)現(xiàn)包括對(duì)取消的檢查,如果在start方法調(diào)用前取消了operation,start方法會(huì)直接退出。推測(cè)start的默認(rèn)實(shí)現(xiàn)里應(yīng)該是先判斷isCancelled,如果是YES就直接return了。
在macOS10.6以后,如果operation有依賴的其他operation還未完成,此時(shí)operation調(diào)用cancel方法,會(huì)忽略掉依賴。如果operation是在隊(duì)列中的,這允許隊(duì)列調(diào)用operation的start方法,以在不調(diào)用opeartion的main方法的情況下從隊(duì)列中刪除操作。

NSInvocationOperation

This class implements a non-concurrent operation.

NSInvocationOperation是非并發(fā)的operation。

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
[operation start];

- (void)test{
    NSLog(@"%@", [NSThread currentThread]);
}

log:

<NSThread: 0x60000007a8c0>{number = 1, name = main}

可以看出任務(wù)是在主線程執(zhí)行的,并沒有生成新的線程。

來(lái)看一下取消的效果:

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
[operation cancel];
NSLog(@"isCancelled == %@, isFinished == %@", operation.isCancelled?@"YES":@"NO",  operation.isFinished?@"YES":@"NO");
[operation start];
NSLog(@"isFinished == %@", operation.isFinished?@"YES":@"NO");

- (void)test{
    NSLog(@"%@", [NSThread currentThread]);
}

log:

isCancelled == YES, isFinished == NO
isFinished == YES

可以看出,operation調(diào)用cancel后,isCancelled(默認(rèn)是NO)會(huì)立即變成YES,而此時(shí)isFinished依然是NO。當(dāng)operation調(diào)用start后,并沒有調(diào)用test方法,并且把isFinished設(shè)置成了YES。

NSBlockOperation

An operation that manages the concurrent execution of one or more blocks.
When executing more than one block, the operation itself is considered finished only when all blocks have finished executing.

雖然文檔里提到了NSBlockOperation是管理block并發(fā)執(zhí)行的,實(shí)際block的并行執(zhí)行和block的數(shù)量有關(guān)。

NSBlockOperation *p = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block1 == %@", [NSThread currentThread]);
}];
NSLog(@"isAsynchronous == %@", p.isAsynchronous?@"YES":@"NO");
[p start];

log:

isAsynchronous == NO
block1 == <NSThread: 0x604000066c40>{number = 1, name = main}

可以看出,block是在主線程里執(zhí)行的,并沒有生成新的線程。
如果再添加一個(gè)一個(gè)block的話:

 NSBlockOperation *p = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block1  %@", [NSThread currentThread]);
 }];
 [p addExecutionBlock:^{
        sleep(3);
        NSLog(@"block2 %@", [NSThread currentThread]);
 }];
 NSLog(@"isAsynchronous == %@", p.isAsynchronous?@"YES":@"NO");
 [p start];
 NSLog(@"operation finish");

log:

isAsynchronous == NO
block1  <NSThread: 0x604000261ac0>{number = 1, name = main}
block2 <NSThread: 0x600000461a00>{number = 3, name = (null)}
operation finish

isAsynchronous == NO,雖然兩個(gè)block是并發(fā)執(zhí)行的,但是start方法會(huì)阻塞調(diào)用start的線程。blcok1是在主線程中完成的,block2則是在一個(gè)新的線程中完成的,并且NSBlockOperation在調(diào)用start方法后會(huì)阻塞當(dāng)前線程,等到所有的block執(zhí)行完后才會(huì)繼續(xù)執(zhí)行后面的代碼。參考
NSBlockOperation的cancel和NSInvocationOperation是一樣的,調(diào)用cancel后把isCancelled設(shè)置為YES,然后再調(diào)用start把isFinished設(shè)置為YES。
總結(jié)一下,就是NSOperation的cancel方法需要把isCancelled屬性設(shè)置為YES。start方法里需要判斷isCancelled屬性,如果是YES就不執(zhí)行任務(wù),并且把isFinished設(shè)置為YES。

The default implementation of this method updates the execution state of the operation and calls the receiver’s main method....if the receiver was cancelled or is already finished, this method simply returns without calling main.
上面是start方法的描述,提到了start方法會(huì)調(diào)用main方法,如果operation的isCancelled或isFinished是YES,則start方法會(huì)直接返回不會(huì)調(diào)用main方法。

自定義operation要考慮定義的operation是concurrent還是non-concurrent,這里的并發(fā)是相對(duì)于調(diào)用start方法的線程。

non-concurrent operations

For non-concurrent operations, you typically override only one method:

  • main
    文檔里提到了,要自定義一個(gè)非并行的operation,
    只需要重寫main方法即可。
@interface OQNonConcurrentOperation : NSOperation

@end
@interface OQNonConcurrentOperation ()

@end

@implementation OQNonConcurrentOperation
- (void)main {
    @try {
        if (self.isCancelled) return;
        
        NSLog(@"Start executing mainThread: %@, currentThread: %@",  [NSThread mainThread], [NSThread currentThread]);
        
        for (NSUInteger i = 0; i < 3; i++) {
            if (self.isCancelled) return;
            
            sleep(1);
            
            NSLog(@"Loop %@", @(i + 1));
        }
        NSLog(@"Finish executing %@", NSStringFromSelector(_cmd));
    }
    @catch(NSException *exception) {
        NSLog(@"Exception: %@", exception);
    }
}

@end

上面main方法的代碼里使用了isCancelled。因?yàn)閏ancel方法不會(huì)強(qiáng)制停止operation的代碼執(zhí)行,所以如果operation正在執(zhí)行main方法時(shí)被取消了,可以在執(zhí)行任務(wù)的代碼里加上isCancelled的判斷來(lái)退出執(zhí)行的任務(wù)。

concurrent operation

If you are creating a concurrent operation, you need to override the following methods and properties at a minimum:

要自定義一個(gè)concurrent operation,至少需要重寫上面的方法和屬性。

At no time in your start method should you ever call super. When you define a concurrent operation, you take it upon yourself to provide the same behavior that the default start method provides, which includes starting the task and generating the appropriate KVO notifications. Your start method should also check to see if the operation itself was cancelled before actually starting the task. For more information about cancellation semantics

If you are implementing a concurrent operation, you must override this method and use it to initiate your operation. Your custom implementation must not call super at any time. In addition to configuring the execution environment for your task, your implementation of this method must also track the state of the operation and provide appropriate state transitions. When the operation executes and subsequently finishes its work, it should generate KVO notifications for the isExecuting and isFinished key paths respectively. For more information about manually generating KVO notifications,

文檔里里提到了,自定義的concurrent operation,在重寫start的方法里,不能調(diào)用super,并且要自己提供start方法里一些默認(rèn)的行為,包括在開啟任務(wù)前查看任務(wù)是否被取消;開啟任務(wù)(由于任務(wù)封裝在main方法里,可以理解為調(diào)用main方法);修改狀態(tài)屬性時(shí)手動(dòng)生成KVO通知等。

@interface OQConcurrentOperation : NSOperation

@end
@implementation OQConcurrentOperation

@synthesize executing = _executing;
@synthesize finished  = _finished;

- (id)init {
    self = [super init];
    if (self) {
        _executing = NO;
        _finished  = NO;
    }
    return self;
}

- (BOOL)isConcurrent {
    return YES;
}

- (BOOL)isExecuting {
    return _executing;
}

- (BOOL)isFinished {
    return _finished;
}

- (void)start {
   //在開啟任務(wù)前判斷operation是否已經(jīng)被取消
    if (self.isCancelled) {
        //如果已經(jīng)取消任務(wù)則把_finished設(shè)置成YES,并生成KVO通知,然后return。
        [self willChangeValueForKey:@"isFinished"];
        _finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }
    //沒有取消則異步開啟任務(wù)
    [self willChangeValueForKey:@"isExecuting"];
    
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    _executing = YES;
    
    [self didChangeValueForKey:@"isExecuting"];
}

- (void)main {
    @try {
        NSLog(@"Start executing %@, mainThread: %@, currentThread: %@", NSStringFromSelector(_cmd), [NSThread mainThread], [NSThread currentThread]);

        sleep(3);
        
        [self willChangeValueForKey:@"isExecuting"];
        _executing = NO;
        [self didChangeValueForKey:@"isExecuting"];

        [self willChangeValueForKey:@"isFinished"];
        _finished  = YES;
        [self didChangeValueForKey:@"isFinished"];

        NSLog(@"Finish executing %@", NSStringFromSelector(_cmd));
    }
    @catch (NSException *exception) {
        NSLog(@"Exception: %@", exception);
    }
}
@end

NSOPerationQueue

 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
OQConcurrentOperation *p =  [[OQConcurrentOperation alloc] init];
 [queue addOperation:p];

在OQConcurrentOperation的start的方法里加入:

 NSLog(@" %@ method currentThread: %@", NSStringFromSelector(_cmd), [NSThread currentThread]);

log:

start method currentThread: <NSThread: 0x600000473d80>{number = 4, name = (null)}

從logke已看出,operation queue會(huì)生成新的線程來(lái)執(zhí)行加入到其中的operation,執(zhí)行的過程也是調(diào)用start方法。

queuePriority

For operations that are ready to execute, the operation queue always executes the one with the highest priority relative to the other ready operations.

queuePriority決定了operation在隊(duì)列中執(zhí)行的優(yōu)先級(jí)。通過以下的順序設(shè)置queuePriority屬性可以加快或者推遲操作的執(zhí)行:

NSOperationQueuePriorityVeryHigh
NSOperationQueuePriorityHigh
NSOperationQueuePriorityNormal
NSOperationQueuePriorityLow
NSOperationQueuePriorityVeryLow

依賴性

根據(jù)你應(yīng)用的復(fù)雜度不同,將大任務(wù)再分成一系列子任務(wù)一般都是很有意義的,而你能通過NSOperation的依賴性實(shí)現(xiàn)。

比如說(shuō),對(duì)于服務(wù)器下載并壓縮一張圖片的整個(gè)過程,你可能會(huì)將這個(gè)整個(gè)過程分為兩個(gè)操作。顯然圖片需要等到下載完成之后才能被調(diào)整尺寸,所以我們定義網(wǎng)絡(luò)操作是壓縮操作的依賴,通過代碼來(lái)說(shuō)就是:

[resizingOperation addDependency:networkingOperation];
[operationQueue addOperation:networkingOperation];
[operationQueue addOperation:resizingOperation];

這樣resizingOperation會(huì)等待其依賴的networkingOperation完成(isFinished返回YES)之后才會(huì)執(zhí)行。
此外,確保不要意外地創(chuàng)建依賴循環(huán),像A依賴B,B又依賴A,這也會(huì)導(dǎo)致杯具的死鎖。

從operation queue里移除operation

The NSOperationQueue class regulates the execution of a set of NSOperation objects. After being added to a queue, an operation remains in that queue until it is explicitly canceled or finishes executing its task.

文檔里提到了在隊(duì)列里的operation被取消或完成時(shí)會(huì)從隊(duì)列里移除。

在的start開始加入

NSLog(@"isCancelled == %@  currentThread: %@", self.isCancelled?@"YES":@"NO", [NSThread currentThread]);
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
OQConcurrentOperation *operation =  [[OQConcurrentOperation alloc] init];
[queue addOperation:p];
[operation cancel];

log:

isCancelled == YES  currentThread: <NSThread: 0x6000004706c0>{number = 4, name = (null)}

雖然operation被取消了,但是operation queue依然生成了新的線程并調(diào)用了start方法,因此取消operation并不會(huì)把operation從operation queue里移除。

文檔里后面也提到了:

You cannot directly remove an operation from a queue after it has been added. An operation remains in its queue until it reports that it is finished with its task. Finishing its task does not necessarily mean that the operation performed that task to completion. An operation can also be canceled. Canceling an operation object leaves the object in the queue but notifies the object that it should abort its task as quickly as possible. For currently executing operations, this means that the operation object’s work code must check the cancellation state, stop what it is doing, and mark itself as finished. For operations that are queued but not yet executing, the queue must still call the operation object’s start method so that it can processes the cancellation event and mark itself as finished.

opertion會(huì)等待任務(wù)完成后才被移除。取消operation會(huì)把operation留在隊(duì)列里但是會(huì)通知operation應(yīng)該終止任務(wù)(通過isCancelled判斷是否終止任務(wù))。如果operation依然在隊(duì)列里但是還沒有執(zhí)行,取消后,隊(duì)列依然會(huì)生成新的線程且operation會(huì)在線程里調(diào)用start方法。

In macOS 10.6 and later, if you call the cancel method on an operation that is in an operation queue and has unfinished dependent operations, those dependent operations are subsequently ignored. Because the operation is already cancelled, this behavior allows the queue to call the operation’s start method to remove the operation from the queue without calling its main method.

文檔里提到了,在macOS 10.6以后,如果讓一個(gè)在operation queue里的operation調(diào)用了cancel方法,如果這個(gè)operation有其他的依賴,則這些依賴會(huì)被忽略。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
OQConcurrentOperation *operation1 =  [[OQConcurrentOperation alloc] init];
NSBlockOperation * operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block1 == %@", [NSThread currentThread]);
        sleep(3);
    }];
[operation1 addDependency: operation2];
[queue addOperation: operation1];
[queue addOperation: operation2];

log:

14:12:59.709827+0800 OperationQueues[27342:5488772] block1 == <NSThread: 0x604000477100>{number = 4, name = (null)}
14:13:02.712193+0800 OperationQueues[27342:5488770] isCancelled == NO  currentThread: <NSThread: 0x600000671140>{number = 5, name = (null)}

從時(shí)間上可以看出第二個(gè)log晚了3s,也就是operation1是在operation2之后執(zhí)行的。

如果把operation1取消了:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
OQConcurrentOperation *operation1 =  [[OQConcurrentOperation alloc] init];
NSBlockOperation * operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block1 == %@", [NSThread currentThread]);
        sleep(3);
    }];
[operation1 addDependency: operation2];
[queue addOperation: operation1];
[queue addOperation: operation2];
[operation1 cancel];

log:

14:41:09.661578+0800 OperationQueues[27915:5821739] isCancelled == YES  currentThread: <NSThread: 0x600000477540>{number = 4, name = (null)}
14:41:09.661577+0800 OperationQueues[27915:5821721] block1 == <NSThread: 0x604000477040>{number = 5, name = (null)

由于operation1被取消了,它的依賴關(guān)系也都被忽略了,因此沒有等待operation2完成,而是直接在新的線程里調(diào)用了start方法。
在隊(duì)列里的operation什么時(shí)候被移除

maxConcurrentOperationCount

The maximum number of queued operations that can execute at the same

maxConcurrentOperationCount表示隊(duì)列同一時(shí)刻做多執(zhí)行的operation的數(shù)量。如果maxConcurrentOperationCount設(shè)置為2,則隊(duì)列同時(shí)最多執(zhí)行兩個(gè)operation,當(dāng)有一個(gè)operation完成時(shí),會(huì)執(zhí)行其他的operation。operation完成是通過isFinished判斷的。
看一下下面的代碼:

@interface CustomOperation : NSOperation

@end

@implementation CustomOperation
@synthesize finished = _finished;

- (void)start{
 NSLog(@"%@", [NSThread currentThread]);  
}
NSOperationQueue *q = [[NSOperationQueue alloc] init];
q.maxConcurrentOperationCount = 2;
for (int i = 0; i < 10; i++) {
        CustomOperation *p = [[CustomOperation alloc] init];
       [q addOperation:p];
}

log:

<NSThread: 0x60400067f1c0>{number = 4, name = (null)}
<NSThread: 0x600000472140>{number = 5, name = (null)}

由于沒有設(shè)置完成狀態(tài),即使start方法掉用完后隊(duì)列依然沒有執(zhí)行其它的operation,打印了兩個(gè)log之后就不再打印了。
把上面的start方法改一下:

- (void)start{
    NSLog(@"%@", [NSThread currentThread]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self willChangeValueForKey:@"isFinished"];
        _finished = YES;
        [self didChangeValueForKey:@"isFinished"];
    });
}

log:

17:09:51 <NSThread: 0x60000027ee00>{number = 3, name = (null)}
17:09:51 <NSThread: 0x60400047a900>{number = 4, name = (null)}
17:09:54 <NSThread: 0x60400047a900>{number = 4, name = (null)}
17:09:54 <NSThread: 0x6000006699c0>{number = 5, name = (null)}
17:09:57 <NSThread: 0x60400047a900>{number = 4, name = (null)}
17:09:57 <NSThread: 0x60000027ee00>{number = 3, name = (null)}
17:10:01 <NSThread: 0x6000006699c0>{number = 5, name = (null)}
17:10:01 <NSThread: 0x60400047a900>{number = 4, name = (null)}
17:10:04 <NSThread: 0x60000027ee00>{number = 3, name = (null)}
17:10:04 <NSThread: 0x60400047a900>{number = 4, name = (null)}

從log可以看出,當(dāng)把_finished設(shè)置為YES并且手動(dòng)發(fā)出KVO通知時(shí)后,隊(duì)列才會(huì)繼續(xù)執(zhí)行其它的operation。

參考鏈接

http://blog.leichunfeng.com/blog/2015/07/29/ios-concurrency-programming-operation-queues/

NSOperation在SDWebImage里的應(yīng)用

SDWebImage有一個(gè)繼承自NSOperation的類SDWebImageDownloaderOperation,圖片的下載和處理封裝在了這個(gè)類里。

SDWebImageDownloaderOperation重寫了start方法和executing、finished屬性,但是沒有重寫asynchronous屬性。
start方法里用到了@synchronized(),線程同時(shí)執(zhí)行這段代碼。
然后判斷了operation是否被取消了:

if (self.isCancelled) {
    self.finished = YES;
    [self reset];
    return;
 }

如果沒有取消operation,則使用NSURLSession生成了NSURLSessionTask,最后將開啟了下載任務(wù)。

self.dataTask = [session dataTaskWithRequest:self.request];
self.dataTask = [session [self.dataTask resume];

SDWebImageDownloaderOperation重寫了cancel方法,方法里取消了NSURLSessionTask并且更新了operataion的狀態(tài):

[super cancel];//更新isCancelled的狀態(tài)
[self.dataTask cancel];
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;

SDWebImageDownloader有一個(gè)屬性

@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;

downloadQueue是用來(lái)添加operation的隊(duì)列。
_downloadQueue.maxConcurrentOperationCount = 6;
代碼里限制了_downloadQueue最多同時(shí)執(zhí)行6個(gè)operation,由于dataTask的代理回調(diào)后才會(huì)把operation的狀態(tài)設(shè)置為完成,所以SDWebImage最多同時(shí)下載6張圖片。

上面是網(wǎng)絡(luò)獲取圖片的過程,SDWebImage從本地緩存那圖片做取消功能時(shí)也使用了NSOperation。
SDWebImage的緩存功能在類SDImageCache里面。SDWebImage的緩存分為內(nèi)存緩存(memory cache)和磁盤緩存(diskCache)。
SDWebImage從緩存里取圖片會(huì)先從內(nèi)存緩存里?。?/p>

//這個(gè)過程是在主線程中進(jìn)行的,并且在內(nèi)存緩存里的圖片是解壓過的。
[self.memCache objectForKey:key]

如果內(nèi)存緩存里沒有,會(huì)從磁盤里取。SDImageCache有一個(gè)屬性_ioQueue 是自動(dòng)定義的串行隊(duì)列:

_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);

從磁盤里取的過程:

NSOperation *operation = [NSOperation new]; dispatch_async(self.ioQueue, ^{
     if (operation.isCancelled) {
          // do not call the completion if cancelled
          return;
     }

     @autoreleasepool {
            ...//取圖片
      }
  });

從磁盤里取圖片的過程封裝成了一個(gè)block添加到了_ioQueue這個(gè)串行隊(duì)列里。這段代碼開始創(chuàng)建了一個(gè)operation,這個(gè)operation在block里被捕獲了,用途是來(lái)通過operation的屬性isCancelled來(lái)取消執(zhí)行block里面的代碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容