iOS NSOperation和NSOperationQueue解讀

NSOperation和NSOperationQueue簡介

NSOperation:

即操作對象,是一個抽象類,用於封裝和單個任務關聯的代碼和數據。因爲它是抽象的,所以不直接使用這個類,而是使用子類(NSInvocationOperation或NSBlockOperation)或使用自定義的子類之一來執行實際任務。
儘管NSOperation是抽象類,但其基本實現重要的邏輯來協調我們任務但安全執行,這種內置的邏輯使得我們可以專注於我們任務的實現,而不是和其他事務邏輯耦合。
NSOperation是一次性對象,即它只會執行一次任務,不能再使用它來執行其他任務。我們通常將操作對象添加到操作隊列(NSOperationQueue類的實例)來同步或異步的執行操作,除了在操作隊列上使用,我們還可以開子線程運行他們或使用結合GCD來執行操作,此時這些操作任務會在當前線程上執行。
如果將NSOperation對象添加到NSOperationQueue隊列上,操作對象們會被操作隊列管理開啓操作,如果單獨使用操作隊列,則需要直接調用其start方法來開啓任務。手動執行操作會給代碼帶來更多的負擔,因爲啓動未處於isReady狀態的操作會觸發異常。

NSOperationQueue:

NSOperationQueue類是管理調用一組NSOperation對象的類。當操作對象加入到隊列,該操作直到被明確cancelledfinished 前都會被保留在隊列中。操作隊列中的操作本身根據優先級和操作間對象依賴性進行組織,並相應的執行。應用程序可能會創建多個操作隊列並將操作提交給它們中的任何一個。
即使這些操作對象位於不同的操作隊列中,操作間的依賴關係也是有效的。操作對象在其所有依賴操作完成執行之前不會進入isReady就緒狀態。對於isReady狀態的操作對象,最高優先級的操作的對象將會被優先執行。
對於已加入到操作隊列裏的操作對象,我們無法將其移除,操作隊列會保持在隊列中,直到其完成其任務,當然,這裏的完成是一種狀態,並不一定意味着真正的完成了其任務,因爲操作對象在未被執行前,是可以取消的。取消操作對象依舊被保留在隊列中,但是會通知對象儘快終止其任務。對於當前正在執行的操作,這意味着操作對象的工作代碼必須檢查取消狀態,停止正在執行的操作,並將自己標記爲finished已完成狀態。對於’isReady’狀態但是尚未執行的操作,隊列仍必須調用該操作對象的start方法,以便它可以處理取消事件並將自己標記爲finished已完成狀態。
取消操作會導致操作忽略一切和它具有依賴關係。這意味着和其有依賴關係的操作可能更早的被執行。
和NSOperation一樣,操作隊列通常會在當前線程。另外,操作隊列會使用GCD來啓動其操作對象的執行,因此,操作對象總是在單獨的線程上執行,而不管它們是否被指定爲異步或同步操作。

上面是官方給出的解釋,總體而言,NSOperation是操作對象類,NSOperationQueue是操作對象的調度者。


NSOperation類

NSOperation類是一個抽象類,該類並沒賦予其完成任務的能力,我們可以使用它的兩個子類NSBlockOperationNSInvocationOperation,前者是通過block的形式完成任務,後者是則使用方法選擇器實現。另外,我們還可以自定義自己的操作類。

  • NSBlockOperation

NSBlockOperation用於管理一個或多個block任務的併發執行,我們可以使用該對象實現一次執行多個block任務,而無需爲每個block任務創建單獨的操作對象。當執行多個block任務時,只有當所有的block完成時才認爲操作本身已完成。

注:添加到block的任務是以默認優先級調度到隊列的,並且block任務內部不可以在去更改執行環境。

-(void)useBlockOperation{
    // 創建操作對象
    NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    // 單獨使用操作對象時需要手動開啓任務
    // 啓動
    [bop start];
}

當我們在主線程中測試時:

 [self useBlockOperation];

主線程

當我們在某線程中測試時:

[NSThread detachNewThreadSelector:@selector(useBlockOperation) toTarget:self withObject:nil];

子線程

上面兩個測試可知:操作對象會在給定的線程中執行任務

我們給NSBlockOperation對象添加更多的block任務,並在主線程中調用

-(void)useBlockOperation{
    // 創建操作對象
    NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"origin:%@",[NSThread currentThread]);
    }];
    for (int i=0; i<10; i++) {
        [bop addExecutionBlock:^{
            NSLog(@"%@:%@",[NSString stringWithFormat:@"%i",i],[NSThread currentThread]);
        }];
        NSLog(@"操作任務是否完成:%@",bop.isFinished?@"YES":@"NO");
    }
    // 啓動
    [bop start];
    NSLog(@"操作任務是否完成:%@",bop.isFinished?@"YES":@"NO");
}

新增任務

我們發現,block中的操作任務是併發執行的,系統會在開啓一個或多個線程執行這些任務,可以看到11任務是在1和3號線程執行的,其中1號線程是主線程,因爲我們是在主線程中調用操作對象的。如果我們在子線程中調用,則不會出現主線程,而會繼續創建子線程去執行任務。另外,我們發現,只有當所有的block完成時,操作本身才會被標記爲已完成狀態。

  • NSInvocationOperation

NSInvocationOperation對象用於管理指定爲調用的單個封裝的任務的執行。我們可以通過該對象開啓一個操作,並指定對象調用選擇器執行任務。另外我們傳遞參數。

// invocation操作對象
[NSThread detachNewThreadSelector:@selector(useInvocationOperation) toTarget:self withObject:nil];

-(void)useInvocationOperation{
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation:) object:@{@"name":@"NSInvocationOperation"}];
    [op start];
}
// 任務
-(void)invocationOperation:(NSDictionary*)dic{
    NSLog(@"%@\n%@",[NSThread currentThread],dic);
}

子線程

我們看到NSInvocationOperation在新開啓的線程被執行

  • 自定義操作類

對於NSOperation類,我們發現,NSOperation的對象執行任務除了BlockOperation中的addExecutionBlock開啓了新的線程,似乎和我們普通對象調用方法沒什麼不同,都是在當前線程執行任務,想要實現多線程編程,都需要藉助GCD或NSTread來開啓子線程,那這個操作類是否顯得非常雞肋呢?當然不是,其實NSOperation是需要結合NSOperationQueue才能發揮其作用的,前者是一個操作對象,後者是基於GCD的封裝對象,用來便捷的完成NSOperation對象執行操作事務的調度工作。

NSOperationQueue

NSOperationQueue對象可以調度NSOperation對象,隊列中的操作對象都是併發執行。

-(void)useOperationQueue{
    NSLog(@"queue:%@",[NSThread currentThread]);
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    for (int i=0; i<3; i++) {
        NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@:%@",[NSString stringWithFormat:@"%i",i],[NSThread currentThread]);
        }];
        // 添加操作隊列
        [queue addOperation:bop];
    }
    // 或者快捷添加操作對象
    [queue addOperationWithBlock:^{
        NSLog(@"Add directly:%@",[NSThread currentThread]);
    }];
}

queue隊列

我們發現,隊列雖然是在主線程中執行,但是操作對象都是運行在子線程中,並且所有的操作對象都不需要手動開啓,都是由隊列自動調度開啓。

關於操作對象的執行順序會根據當前處於isReady狀態的操作對象的優先級調用,另外,設置依賴關係的操作對象會在依賴對象完成後進入isReady狀態,換句話說,被依賴對象優先於依賴對象執行。

首先我們先創建兩個操作對象

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"block:%@",[NSThread currentThread]);
}];
[bop addExecutionBlock:^{
    NSLog(@"blockNew:%@",[NSThread currentThread]);
}];
[bop addExecutionBlock:^{
    NSLog(@"blockNew2:%@",[NSThread currentThread]);
}];
NSInvocationOperation *iop = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation:) object:nil];
[queue addOperations:@[iop,bop] waitUntilFinished:NO];

沒有依賴關係的

我們發現這兩個操作對象的任務都是併發的。

  • 依賴

下面我們添加依賴

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"block:%@",[NSThread currentThread]);
}];
[bop addExecutionBlock:^{
    NSLog(@"blockNew:%@",[NSThread currentThread]);
}];
[bop addExecutionBlock:^{
    NSLog(@"blockNew2:%@",[NSThread currentThread]);
}];
NSInvocationOperation *iop = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation:) object:nil];
// 添加依賴關係
[iop addDependency:bop];
[queue addOperations:@[iop,bop] waitUntilFinished:NO];

添加依賴

我們發現,由於iop依賴bop,所以iop始終會等到bop完成後才被執行。

注:添加依賴時,不要出現循環依賴的情況,會導致死鎖,死鎖的兩個操作對象都等待對方完成,結果都不能執行。

  • 死鎖

另外,如果A依賴B,B手動先於A執行,系統則會拋出異常。

[iop addDependency:bop];
[iop start];

異常操作

  • 等待所有操作完成

如果我們需要再所有操作任務完成後執行一些事務,可以再添加操作對象數組的時候設置等待

[queue addOperations:@[iop,bop] waitUntilFinished:YES];

或者

[queue waitUntilAllOperationsAreFinished];

該方法會阻塞當前線程,等待所有操作對象完成任務。在此期間,當前線程不能再往隊列中添加操作對象,當然,其他線程可以添加操作對象,一旦所有掛起的操作對象完成,此方法返回,如果隊列中沒有操作對象,則此方法立刻返回。

  • 最大線程
@property NSInteger maxConcurrentOperationCount;

默認爲-1,不限制,當設置爲1時,相當於一個同步執行的操作隊列。


其他的屬性/方法

  • NSOperation

可監聽的一些屬性:

// 操作已取消
@property (readonly, getter=isCancelled) BOOL cancelled;
// 操作正在執行
@property (readonly, getter=isExecuting) BOOL executing;
// 操作已完成
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
// 任務是併發還是同步執行,當操作任務加入到操作隊列後,會忽略該屬性
@property (readonly, getter=isAsynchronous) BOOL asynchronous ;
// 任務是否已經就緒,當其依賴的操作任務都執行完時,改狀態纔會是YES
@property (readonly, getter=isReady) BOOL ready;

添加/移除依賴

- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;

開啓/取消操作任務

- (void)start;
- (void)cancel;

優先級

@property NSOperationQueuePriority queuePriority;
// 取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

阻塞當前線程

- (void)waitUntilFinished;

操作完成回調

@property (nullable, copy) void (^completionBlock)(void);
  • NSOperationQueue

當前操作數量

@property (readonly) NSUInteger operationCount;

暫停/恢復隊列

@property (getter=isSuspended) BOOL suspended;

取消隊列中的操作任務

- (void)cancelAllOperations;

當前隊列/主隊列

@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue;
@property (class, readonly, strong) NSOperationQueue *mainQueue;

我們可以獲取到主隊列,再調用

- (void)addOperationWithBlock:(void (^)(void))block;

完成UI更新。

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.contentImageView.image = image;
        }];
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章