NSOperation
NSOperation表示了一個獨立的任務。NSOperation是一個抽象類。不能直接使用。你可以使用系統(tǒng)定義好的它的兩個字類NSInvocationOperation 和 NSBlockOperation。也可以自己定義一個繼承自NSOperation的類使用。
很多執(zhí)行任務類型的案例都很好的運用了NSOperation,包括AFNetworking,SDWebIamge,圖像壓縮,自然語言處理或者其他很多需要返回處理后數(shù)據(jù)的、可重復的、結構化的、相對長時間運行的任務。
NSOperationQueue控制著NSOperation任務的執(zhí)行。通常是吧一個NSOperation對象添加到NSOperationQueue中,NSOperationQueue會直接在子線程里執(zhí)行添加到其中的NSOperation任務。
如果不使用NSOperationQueue,也可以調(diào)用NSOperation的start方法來執(zhí)行任務。
直接通過調(diào)用 start 方法來執(zhí)行一個 operation ,但是這種方式并不能保證 operation 是異步執(zhí)行的。
isAsynchronous
NSOperation 類的 isAsynchronous 方法的返回值標識了一個 operation 相對于調(diào)用它的 start 方法的線程來說是否是異步執(zhí)行的。在默認情況下,isAsynchronous 方法的返回值是 NO ,也就是說會阻塞調(diào)用它的 start 方法的線程。
如果想要調(diào)用它的 start方法后異步執(zhí)行任務,需要編寫一些額外的代碼來讓這個 operation 異步執(zhí)行。僅僅重寫isAsynchronous返回YES是不會讓任務異步執(zhí)行的,還需要自己創(chuàng)建新的線程來保證任務的異步執(zhí)行。更多信息,可以看Asynchronous Versus Synchronous Operations
系統(tǒng)提供的NSInvocationOperation 和 NSBlockOperation這兩個類的isAsynchronous都是NO,也就是這兩個類都會阻塞調(diào)用start方法的線程。
Asynchronous Versus Synchronous Operations
如果使用start方法來開啟一個任務,默認是同步的。只需要在子類里重寫main方法,在這個方法里寫上你需要執(zhí)行的任務。
如果要用start方法開啟一個異步地任務,則至少需要重寫下面的方法:
start:start方法會開始執(zhí)行NSOperation的任務,這個方法的實現(xiàn)會更新isExecuting的狀態(tài)并且會調(diào)用NSOperation的main方法。如果NSOperation已經(jīng)取消了或者已經(jīng)完成了,這個方法會直接返回,不會調(diào)用面方法。如果這個NSOperation正在執(zhí)行或者還沒有準備好執(zhí)行,這個方法會拋出一個NSInvalidArgumentException異常。
如果這個NSOperation依賴的其他NSOperation尚未完成,則這個NSOperation的isReady是NO。重寫這個方法不能調(diào)用super。這個方法里還需要追蹤NSOperation的狀態(tài)。它應分別手動地為isExecuting和isFinished的keypath生成KVO通知。手動的生成KVO通知
NSOperation已經(jīng)添加到NSOperationQueue后在調(diào)用這個方法或者先調(diào)用了這個方法然后再把NSOperation添加到NSOperationQueue都是錯誤的。
在并發(fā)操作中,stat方法負責以異步地方式開啟NSOperation。無論是創(chuàng)建新的線程還是使用異步函數(shù),都要在start方法里完成。
在開始NSOperation時,start方法里應該更新isExecuting這個屬性來報告NSOperation的狀態(tài),可以通過手動發(fā)一個isExecuting key path KVO通知。
當完成或取消NSOperation時,需要對isExecuting和isFinished手動生成KVO通知。在取消NSOperation的情況下,即使NSOperation沒有完成任務,人需要更新isFinished。因為在隊列中的NSOperation的狀態(tài)完成的情況下才會被移除。
注意:
在start的方法里任何時候都不應該調(diào)用super。在定義并發(fā)的NSOperation時,需要提供默認的start方法提供的行為,其中包括開啟任務和生成KVO通知。start方法話還應該見檢查在任務開啟前NSOperation是否已經(jīng)被取消了。Cancel CommandisAsynchronous:這個只默認是NO,這個值本身不會影響任務同步還是異步。
isExecuting:自定義并發(fā)的NSOperation時,必須要重寫這個屬性。當你改變這個屬性時,需要手動生成kvo通知。
isFinished:自定義并發(fā)的NSOperation時,必須要重寫這個屬性。當你改變這個屬性時,需要手動生成kvo通知。
NSOperation State
和NSOperation的狀態(tài)關聯(lián)的屬性:
- isReady:isReady表示NSOperation什么時候準備好執(zhí)行。它的值是YES表示已經(jīng)準備好執(zhí)行,如果NSOperation有其他依賴的NSOperation還沒有完成,則isReady的值是NO。
大部分情況下,你不需要自己管理這個屬性,如果你自定義的operation的準備狀態(tài)是由依賴操作意外的因素決定的,你可以自己實現(xiàn)這個屬性。 - isExecuting:這個屬性表示NSOperation是否正在執(zhí)行任務。operation正在執(zhí)行任務時是YES,否則是NO。
- isFinished:這個值是YES表示operation的任務完成了或者operation被取消了。當這個值是YES是,opearation queue才會把operation移除隊列,operation的依賴關系也會被清除。
- isCancelled:這個屬性默認值是NO,調(diào)用cancel方法來設置這個值是YES。一旦operation取消了,operation的狀態(tài)要設置為完成。取消operation并不會阻止operation執(zhí)行代碼。如果方法返回YES,operation需要定期調(diào)用此方法并停止。
在完成任務之前,你應該始終檢查這個屬性,通常在自定義的main方法開始時檢查。
cancel operation
cancel方法調(diào)用后會把isCancelled設置為YES。這個方法不會強制停止operation的代碼,它僅僅會更新operation內(nèi)部的標志來反映狀態(tài)的變化。如果operation已經(jīng)完成執(zhí)行了,這個方法不起作用。在operaton queue里的還未開始執(zhí)行任務的operation調(diào)用cancel方法后,可以比平時更快的把operation從operation queue里移除。如果取消不在opeation queue中的operation,則此方法會立即將operation標記為完成狀態(tài)。
NSOperation的默認實現(xiàn)包括對取消的檢查,如果在start方法調(diào)用前取消了operation,start方法會直接退出。推測start的默認實現(xiàn)里應該是先判斷isCancelled,如果是YES就直接return了。
在macOS10.6以后,如果operation有依賴的其他operation還未完成,此時operation調(diào)用cancel方法,會忽略掉依賴。如果operation是在隊列中的,這允許隊列調(diào)用operation的start方法,以在不調(diào)用opeartion的main方法的情況下從隊列中刪除操作。
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}
可以看出任務是在主線程執(zhí)行的,并沒有生成新的線程。
來看一下取消的效果:
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(默認是NO)會立即變成YES,而此時isFinished依然是NO。當operation調(diào)用start后,并沒有調(diào)用test方法,并且把isFinished設置成了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í)行的,實際block的并行執(zhí)行和block的數(shù)量有關。
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í)行的,并沒有生成新的線程。
如果再添加一個一個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,雖然兩個block是并發(fā)執(zhí)行的,但是start方法會阻塞調(diào)用start的線程。blcok1是在主線程中完成的,block2則是在一個新的線程中完成的,并且NSBlockOperation在調(diào)用start方法后會阻塞當前線程,等到所有的block執(zhí)行完后才會繼續(xù)執(zhí)行后面的代碼。參考
NSBlockOperation的cancel和NSInvocationOperation是一樣的,調(diào)用cancel后把isCancelled設置為YES,然后再調(diào)用start把isFinished設置為YES。
總結一下,就是NSOperation的cancel方法需要把isCancelled屬性設置為YES。start方法里需要判斷isCancelled屬性,如果是YES就不執(zhí)行任務,并且把isFinished設置為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方法會調(diào)用main方法,如果operation的isCancelled或isFinished是YES,則start方法會直接返回不會調(diào)用main方法。
自定義operation要考慮定義的operation是concurrent還是non-concurrent,這里的并發(fā)是相對于調(diào)用start方法的線程。
non-concurrent operations
For non-concurrent operations, you typically override only one method:
-
main
文檔里提到了,要自定義一個非并行的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。因為cancel方法不會強制停止operation的代碼執(zhí)行,所以如果operation正在執(zhí)行main方法時被取消了,可以在執(zhí)行任務的代碼里加上isCancelled的判斷來退出執(zhí)行的任務。
concurrent operation
If you are creating a concurrent operation, you need to override the following methods and properties at a minimum:
要自定義一個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方法里一些默認的行為,包括在開啟任務前查看任務是否被取消;開啟任務(由于任務封裝在main方法里,可以理解為調(diào)用main方法);修改狀態(tài)屬性時手動生成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 {
//在開啟任務前判斷operation是否已經(jīng)被取消
if (self.isCancelled) {
//如果已經(jīng)取消任務則把_finished設置成YES,并生成KVO通知,然后return。
[self willChangeValueForKey:@"isFinished"];
_finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
//沒有取消則異步開啟任務
[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會生成新的線程來執(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在隊列中執(zhí)行的優(yōu)先級。通過以下的順序設置queuePriority屬性可以加快或者推遲操作的執(zhí)行:
NSOperationQueuePriorityVeryHigh
NSOperationQueuePriorityHigh
NSOperationQueuePriorityNormal
NSOperationQueuePriorityLow
NSOperationQueuePriorityVeryLow
依賴性
根據(jù)你應用的復雜度不同,將大任務再分成一系列子任務一般都是很有意義的,而你能通過NSOperation的依賴性實現(xiàn)。
比如說,對于服務器下載并壓縮一張圖片的整個過程,你可能會將這個整個過程分為兩個操作。顯然圖片需要等到下載完成之后才能被調(diào)整尺寸,所以我們定義網(wǎng)絡操作是壓縮操作的依賴,通過代碼來說就是:
[resizingOperation addDependency:networkingOperation];
[operationQueue addOperation:networkingOperation];
[operationQueue addOperation:resizingOperation];
這樣resizingOperation會等待其依賴的networkingOperation完成(isFinished返回YES)之后才會執(zhí)行。
此外,確保不要意外地創(chuàng)建依賴循環(huán),像A依賴B,B又依賴A,這也會導致杯具的死鎖。
從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.
文檔里提到了在隊列里的operation被取消或完成時會從隊列里移除。
在的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并不會把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會等待任務完成后才被移除。取消operation會把operation留在隊列里但是會通知operation應該終止任務(通過isCancelled判斷是否終止任務)。如果operation依然在隊列里但是還沒有執(zhí)行,取消后,隊列依然會生成新的線程且operation會在線程里調(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以后,如果讓一個在operation queue里的operation調(diào)用了cancel方法,如果這個operation有其他的依賴,則這些依賴會被忽略。
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)}
從時間上可以看出第二個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被取消了,它的依賴關系也都被忽略了,因此沒有等待operation2完成,而是直接在新的線程里調(diào)用了start方法。
在隊列里的operation什么時候被移除
maxConcurrentOperationCount
The maximum number of queued operations that can execute at the same
maxConcurrentOperationCount表示隊列同一時刻做多執(zhí)行的operation的數(shù)量。如果maxConcurrentOperationCount設置為2,則隊列同時最多執(zhí)行兩個operation,當有一個operation完成時,會執(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)}
由于沒有設置完成狀態(tài),即使start方法掉用完后隊列依然沒有執(zhí)行其它的operation,打印了兩個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可以看出,當把_finished設置為YES并且手動發(fā)出KVO通知時后,隊列才會繼續(xù)執(zhí)行其它的operation。
參考鏈接
http://blog.leichunfeng.com/blog/2015/07/29/ios-concurrency-programming-operation-queues/
NSOperation在SDWebImage里的應用
SDWebImage有一個繼承自NSOperation的類SDWebImageDownloaderOperation,圖片的下載和處理封裝在了這個類里。
SDWebImageDownloaderOperation重寫了start方法和executing、finished屬性,但是沒有重寫asynchronous屬性。
start方法里用到了@synchronized(),線程同時執(zhí)行這段代碼。
然后判斷了operation是否被取消了:
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
如果沒有取消operation,則使用NSURLSession生成了NSURLSessionTask,最后將開啟了下載任務。
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有一個屬性
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
downloadQueue是用來添加operation的隊列。
_downloadQueue.maxConcurrentOperationCount = 6;
代碼里限制了_downloadQueue最多同時執(zhí)行6個operation,由于dataTask的代理回調(diào)后才會把operation的狀態(tài)設置為完成,所以SDWebImage最多同時下載6張圖片。
上面是網(wǎng)絡獲取圖片的過程,SDWebImage從本地緩存那圖片做取消功能時也使用了NSOperation。
SDWebImage的緩存功能在類SDImageCache里面。SDWebImage的緩存分為內(nèi)存緩存(memory cache)和磁盤緩存(diskCache)。
SDWebImage從緩存里取圖片會先從內(nèi)存緩存里?。?/p>
//這個過程是在主線程中進行的,并且在內(nèi)存緩存里的圖片是解壓過的。
[self.memCache objectForKey:key]
如果內(nèi)存緩存里沒有,會從磁盤里取。SDImageCache有一個屬性_ioQueue 是自動定義的串行隊列:
_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 {
...//取圖片
}
});
從磁盤里取圖片的過程封裝成了一個block添加到了_ioQueue這個串行隊列里。這段代碼開始創(chuàng)建了一個operation,這個operation在block里被捕獲了,用途是來通過operation的屬性isCancelled來取消執(zhí)行block里面的代碼。