NSOperation是Apple基于GCD的封裝,面向?qū)ο螅橄髮哟胃撸褂煤唵巍N覀冎恍韪鶕?jù)任務(wù)類型創(chuàng)建合適的NSOperation,配置好相應(yīng)的參數(shù),丟到NSOperationQueue中,剩下的工作就交給系統(tǒng)調(diào)度處理,我們無需關(guān)心。是不是很誘人?
本篇文章主要內(nèi)容:
- NSOperation和NSOperationQueue簡介
- **創(chuàng)建操作對象的三種方式 **
- 設(shè)置操作的優(yōu)先級
- 設(shè)置操作的依賴
- 獲取操作的狀態(tài)
- 取消操作
- 暫停和恢復(fù)操作隊列
- 操作完成的回調(diào)
NSOperation和NSOperationQueue簡介
NSOperation和NSOperationQueue理解起來很容易,我們可以參照GCD相關(guān)內(nèi)容:NSOperation相當(dāng)于GCD中的任務(wù)塊,而NSOperationQueue相當(dāng)于GCD中的并發(fā)隊列。使用NSOperation編程用到的類主要有NSOperation、NSBlockOperation、NSInvocationOperation和NSOperationQueue。NSBlockOperation和NSInvocationOperation是NSOperation的子類,我們不會使用NSOperation這個抽象基類,而是使用NSBlockOperation和NSInvocationOperation創(chuàng)建我們的任務(wù)。下面的內(nèi)容會具體介紹這些類的使用。代碼地址
創(chuàng)建操作對象的三種方式
1.NSInvocationOperation
初始化方法:
initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
代碼示例:
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(sayHello) object:nil];
- (void)sayHello
{
NSLog(@"Hello World");
}
如果我們創(chuàng)建了一個NSInvocationOperation對象,不放入隊列中執(zhí)行,而是直接調(diào)用start方法會是怎樣的呢?
NSInvocationOperation *downloadOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadPic) object:nil];
[downloadOperation start];
NSLog(@"complete");
- (void)downloadPic
{
NSLog(@"start download");
[NSThread sleepForTimeInterval:2.0];
}
運行后發(fā)現(xiàn):打印 "start download" 后,過了2秒才打印 "complete",可以看出沒有放入隊列而直接調(diào)用start運行的操作,會阻塞當(dāng)前線程,是同步的,只有添加到隊列后,操作才會異步執(zhí)行。下面的NSBlockOperation同理。
2.NSBlockOperation
初始化方法:
blockOperationWithBlock:(void (^)(void))block;
添加操作:
addExecutionBlock:(void (^)(void))block;
代碼示例:
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start download ------ %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];
}];
[blockOperation addExecutionBlock:^{
NSLog(@"hello1 ------ %@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"hello2 ------ %@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"hello3 ------ %@", [NSThread currentThread]);
}];
[blockOperation start];
NSLog(@"complete------ %@", [NSThread currentThread]);
從運行結(jié)果可以看出,通過blockOperationWithBlock創(chuàng)建的操作永遠(yuǎn)在主線程執(zhí)行,addExecutionBlock添加的其他操作會分發(fā)到子線程執(zhí)行。
3.繼承自NSOperation
有些情況下,前面兩種方式不能滿足我們的需求,就需要自定義NSOperation了。我們可以創(chuàng)建兩種類型的NSOperation:非并發(fā)的和并發(fā)的。本人能力有限,恐介紹不全,想要徹底吃透這塊需要一定時間,建議大家直接參照Apple提供的官方文檔及示例代碼。
設(shè)置操作的優(yōu)先級
有時候我們想提前或推遲一些操作的執(zhí)行,就可以通過設(shè)置操作的優(yōu)先級來達到目的。操作的優(yōu)先級分為以下幾個級別:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
我們需要注意的是:操作的優(yōu)先級是相對于同一個操作隊列中的其他操作而言的,不同操作隊列中的操作的優(yōu)先級沒有可比性。例如:操作隊列1中的操作A的優(yōu)先級為NSOperationQueuePriorityVeryHigh,操作隊列2中的操作B的優(yōu)先級為NSOperationQueuePriorityVeryLow,至于A和B誰先執(zhí)行,完全不確定。
代碼示例:
NSBlockOperation *downloadPicOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start download picture ------ %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];//模擬下載操作
}];
[downloadPicOperation setQueuePriority:NSOperationQueuePriorityVeryLow];
NSBlockOperation *downloadMusicOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start download music ------ %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:4.0];//模擬下載操作
}];
[downloadMusicOperation setQueuePriority:NSOperationQueuePriorityVeryHigh];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:downloadPicOperation];
[queue addOperation:downloadMusicOperation];
多次運行代碼,我們發(fā)現(xiàn)優(yōu)先級高的不一定會早于優(yōu)先級低的任務(wù)執(zhí)行,這就是我們需要注意的另外一點:優(yōu)先級高的操作先執(zhí)行的概率大,但并不表示必然先執(zhí)行。設(shè)置優(yōu)先級的代碼要在操作放入操作隊列之前,否則是不起作用的或者說會產(chǎn)生不利的影響。
設(shè)置操作的依賴
在并發(fā)編程中,如果任務(wù)之間沒有執(zhí)行的先后關(guān)系,那么它們并發(fā)執(zhí)行是沒問題的,但還有一種情況是某個任務(wù)的執(zhí)行需要其他任務(wù)的執(zhí)行結(jié)果,這時候就要通過設(shè)置操作的依賴來達到目的,類似于GCD的同步。
代碼示例:
- (void)startAction
{
NSBlockOperation *downloadPicOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start download picture ------ %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];//模擬下載操作
}];
NSBlockOperation *prepareOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"prepare download picture ------ %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];//模擬下載前的準(zhǔn)備工作
}];
[downloadPicOperation addDependency:prepareOperation];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:downloadPicOperation];
[queue addOperation:prepareOperation];
}
如代碼所示,我們對下載圖片操作添加了依賴,無論運行多少次,下載操作都是等到準(zhǔn)備操作執(zhí)行完成后才執(zhí)行的。當(dāng)然,對操作添加依賴也可以發(fā)生在不同隊列中,需要注意的是不能形成循環(huán)依賴關(guān)系,會導(dǎo)致死鎖。設(shè)置依賴的代碼要在操作放入操作隊列之前,否則是不起作用的或者說會產(chǎn)生不利的影響。與添加依賴對應(yīng)的操作是移除依賴:
- (void)removeDependency:(NSOperation *)op;
獲取操作的狀態(tài)
操作的生命周期可以表示為:ready —> excute —> finish ,我們將操作放入操作隊列后,操作的isReady狀態(tài)取決于其依賴是否都已執(zhí)行完成,操作的isExecuting狀態(tài)表示操作正在執(zhí)行,操作的isFinished狀態(tài)表示操作已經(jīng)執(zhí)行完成或者被cancel掉了。操作同一時刻只可能是以上三種狀態(tài)中的一種。
取消操作
取消單個操作,我們可以調(diào)用cancel方法;取消操作隊列中的所有操作,我們可以調(diào)用cancelAllOperations方法。我們最好是在確定不需要某個操作的時候才取消它,因為一旦取消,這個操作就被作為finished處理。
暫停和恢復(fù)操作隊列
暫停操作隊列:
[queue setSuspended:YES];
恢復(fù)操作隊列:
[queue setSuspended:NO];
當(dāng)我們暫停某個操作隊列時,操作隊列就會停止調(diào)度新的操作執(zhí)行,而正在執(zhí)行的操作不會被停止。
操作完成的回調(diào)
代碼示例:
- (void)startAction
{
NSBlockOperation *downloadPicOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start download picture ------ %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];//模擬下載操作
}];
[downloadPicOperation setCompletionBlock:^{
NSLog(@"download picture complete");
}];
NSBlockOperation *downloadMusicOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start download music ------ %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:4.0];//模擬下載操作
}];
[downloadMusicOperation setCompletionBlock:^{
NSLog(@"download music complete");
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:downloadPicOperation];
[queue addOperation:downloadMusicOperation];
}
操作完成的回調(diào)可以用于通知主線程任務(wù)完成,注意:如果在回調(diào)中更新UI,需要派發(fā)到主線程執(zhí)行。
至此,iOS多線程之NSOperation相關(guān)內(nèi)容就總結(jié)完了,加上之前的兩篇:iOS多線程之GCD、iOS多線程之pthread和NSThread,iOS并發(fā)編程基本就覆蓋全面了。學(xué)以致用,希望自己在以后的實踐中不斷地總結(jié)提升。