NSOperation
一、NSOperation
1.簡介
NSOperation實例封裝了需要執行的操作和執行操作所需的數據,並且能夠以並發或非並發的方式執行這個操作。
NSOperation本身是抽象基類,因此必須使用它的子類,使用NSOperation子類的方式有2種:
1> Foundation框架提供了兩個具體子類直接供我們使用:NSInvocationOperation和NSBlockOperation
2> 自定義子類繼承NSOperation,實現內部相應的方法
2.執行操作
NSOperation調用start方法即可開始執行操作,NSOperation對象默認按同步方式執行,也就是在調用start方法的那個線程中直接執行。NSOperation對象的isConcurrent方法會告訴我們這個操作相對於調用start方法的線程,是同步還是異步執行。isConcurrent方法默認返回NO,表示操作與調用線程同步執行
3.取消操作
operation開始執行之後, 默認會一直執行操作直到完成,我們也可以調用cancel方法中途取消操作
復制代碼 代碼如下:
[operation cancel];
4.監聽操作的執行
如果我們想在一個NSOperation執行完畢後做一些事情,就調用NSOperation的setCompletionBlock方法來設置想做的事情
復制代碼 代碼如下:
operation.completionBlock = ^() {
NSLog(@"執行完畢");
};
或者
復制代碼 代碼如下:
[operation setCompletionBlock:^() {
NSLog(@"執行完畢");
}];
二、NSInvocationOperation
1.簡介
基於一個對象和selector來創建操作。如果你已經有現有的方法來執行需要的任務,就可以使用這個類
2.創建並執行操作
復制代碼 代碼如下:
// 這個操作是:調用self的run方法
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// 開始執行任務(同步執行)
[operation start];
三、NSBlockOperation
1.簡介
能夠並發地執行一個或多個block對象,所有相關的block都執行完之後,操作才算完成
2.創建並執行操作
復制代碼 代碼如下:
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"執行了一個新的操作,線程:%@", [NSThread currentThread]);
}];
// 開始執行任務(這裡還是同步執行)
[operation start];
3.通過addExecutionBlock方法添加block操作
復制代碼 代碼如下:
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"執行第1次操作,線程:%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^() {
NSLog(@"又執行了1個新的操作,線程:%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^() {
NSLog(@"又執行了1個新的操作,線程:%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^() {
NSLog(@"又執行了1個新的操作,線程:%@", [NSThread currentThread]);
}];
// 開始執行任務
[operation start];
打印信息如下:
2013-02-02 21:38:46.102 thread[4602:c07] 又執行了1個新的操作,線程:<NSThread: 0x7121d50>{name = (null), num = 1} 2013-02-02 21:38:46.102 thread[4602:3f03] 又執行了1個新的操作,線程:<NSThread: 0x742e1d0>{name = (null), num = 5} 2013-02-02 21:38:46.102 thread[4602:1b03] 執行第1次操作,線程:<NSThread: 0x742de50>{name = (null), num = 3} 2013-02-02 21:38:46.102 thread[4602:1303] 又執行了1個新的操作,線程:<NSThread: 0x7157bf0>{name = (null), num = 4}
可以看出,這4個block是並發執行的,也就是在不同線程中執行的,num屬性可以看成是線程的id
四、自定義NSOperation
1.簡介
如果NSInvocationOperation和NSBlockOperation對象不能滿足需求, 你可以直接繼承NSOperation, 並添加任何你想要的行為。繼承所需的工作量主要取決於你要實現非並發還是並發的NSOperation。定義非並發的NSOperation要簡單許多,只需要重載-(void)main這個方法,在這個方法裡面執行主任務,並正確地響應取消事件; 對於並發NSOperation, 你必須重寫NSOperation的多個基本方法進行實現(這裡暫時先介紹非並發的NSOperation)
2.非並發的NSOperation
比如叫做DownloadOperation,用來下載圖片
1> 繼承NSOperation,重寫main方法,執行主任務
DownloadOperation.h
復制代碼 代碼如下:
#import <Foundation/Foundation.h>
@protocol DownloadOperationDelegate;
@interface DownloadOperation : NSOperation
// 圖片的url路徑
@property (nonatomic, copy) NSString *imageUrl;
// 代理
@property (nonatomic, retain) id<DownloadOperationDelegate> delegate;
- (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate;
@end
復制代碼 代碼如下:
// 圖片下載的協議
@protocol DownloadOperationDelegate <NSObject>
- (void)downloadFinishWithImage:(UIImage *)image;
@end
DownloadOperation.m
復制代碼 代碼如下:
#import "DownloadOperation.h"
@implementation DownloadOperation
@synthesize delegate = _delegate;
@synthesize imageUrl = _imageUrl;
// 初始化
- (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate {
if (self = [super init]) {
self.imageUrl = url;
self.delegate = delegate;
}
return self;
}
// 釋放內存
- (void)dealloc {
[super dealloc];
[_delegate release];
[_imageUrl release];
}
// 執行主任務
- (void)main {
// 新建一個自動釋放池,如果是異步執行操作,那麼將無法訪問到主線程的自動釋放池
@autoreleasepool {
// ....
}
}
@end
2> 正確響應取消事件
operation開始執行之後,會一直執行任務直到完成,或者顯式地取消操作。取消可能發生在任何時候,甚至在operation執行之前。盡管NSOperation提供了一個方法,讓應用取消一個操作,但是識別出取消事件則是我們自己的事情。如果operation直接終止, 可能無法回收所有已分配的內存或資源。因此operation對象需要檢測取消事件,並優雅地退出執行
NSOperation對象需要定期地調用isCancelled方法檢測操作是否已經被取消,如果返回YES(表示已取消),則立即退出執行。不管是自定義NSOperation子類,還是使用系統提供的兩個具體子類,都需要支持取消。isCancelled方法本身非常輕量,可以頻繁地調用而不產生大的性能損失
以下地方可能需要調用isCancelled:
* 在執行任何實際的工作之前
* 在循環的每次迭代過程中,如果每個迭代相對較長可能需要調用多次
* 代碼中相對比較容易中止操作的任何地方
DownloadOperation的main方法實現如下
復制代碼 代碼如下:
- (void)main {
// 新建一個自動釋放池,如果是異步執行操作,那麼將無法訪問到主線程的自動釋放池
@autoreleasepool {
if (self.isCancelled) return;
// 獲取圖片數據
NSURL *url = [NSURL URLWithString:self.imageUrl];
NSData *imageData = [NSData dataWithContentsOfURL:url];
if (self.isCancelled) {
url = nil;
imageData = nil;
return;
}
// 初始化圖片
UIImage *image = [UIImage imageWithData:imageData];
if (self.isCancelled) {
image = nil;
return;
}
if ([self.delegate respondsToSelector:@selector(downloadFinishWithImage:)]) {
// 把圖片數據傳回到主線程
[(NSObject *)self.delegate performSelectorOnMainThread:@selector(downloadFinishWithImage:) withObject:image waitUntilDone:NO];
}
}
}
NSOperationQueue
一、簡介
一個NSOperation對象可以通過調用start方法來執行任務,默認是同步執行的。也可以將NSOperation添加到一個NSOperationQueue(操作隊列)中去執行,而且是異步執行的。
創建一個操作隊列:
復制代碼 代碼如下:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
二、添加NSOperation到NSOperationQueue中
1.添加一個operation
復制代碼 代碼如下:
[queue addOperation:operation];
2.添加一組operation
復制代碼 代碼如下:
[queue addOperations:operations waitUntilFinished:NO];
3.添加一個block形式的operation
復制代碼 代碼如下:
[queue addOperationWithBlock:^() {
NSLog(@"執行一個新的操作,線程:%@", [NSThread currentThread]);
}];
NSOperation添加到queue之後,通常短時間內就會得到運行。但是如果存在依賴,或者整個queue被暫停等原因,也可能需要等待。
注意:NSOperation添加到queue之後,絕對不要再修改NSOperation對象的狀態。因為NSOperation對象可能會在任何時候運行,因此改變NSOperation對象的依賴或數據會產生不利的影響。你只能查看NSOperation對象的狀態, 比如是否正在運行、等待運行、已經完成等
三、添加NSOperation的依賴對象
1.當某個NSOperation對象依賴於其它NSOperation對象的完成時,就可以通過addDependency方法添加一個或者多個依賴的對象,只有所有依賴的對象都已經完成操作,當前NSOperation對象才會開始執行操作。另外,通過removeDependency方法來刪除依賴對象。
復制代碼 代碼如下:
[operation2 addDependency:operation1];
依賴關系不局限於相同queue中的NSOperation對象,NSOperation對象會管理自己的依賴, 因此完全可以在不同的queue之間的NSOperation對象創建依賴關系
唯一的限制是不能創建環形依賴,比如A依賴B,B依賴A,這是錯誤的
2.依賴關系會影響到NSOperation對象在queue中的執行順序,看下面的例子:
1> 沒有設置依賴關系
復制代碼 代碼如下:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"執行第1次操作,線程:%@", [NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"執行第2次操作,線程:%@", [NSThread currentThread]);
}];
[queue addOperation:operation1];
[queue addOperation:operation2];
打印信息:
2013-02-03 00:21:35.024 thread[5616:3d13] 執行第1次操作,線程:<NSThread: 0x7658570>{name = (null), num = 3} 2013-02-03 00:21:35.063 thread[5616:1303] 執行第2次操作,線程:<NSThread: 0x765a2e0>{name = (null), num = 4}
可以看出,默認是按照添加順序執行的,先執行operation1,再執行operation2
2> 設置了依賴關系
復制代碼 代碼如下:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"執行第1次操作,線程:%@", [NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"執行第2次操作,線程:%@", [NSThread currentThread]);
}];
// operation1依賴於operation2
[operation1 addDependency:operation2];
[queue addOperation:operation1];
[queue addOperation:operation2];
打印信息:
2013-02-03 00:24:16.260 thread[5656:1b03] 執行第2次操作,線程:<NSThread: 0x7634490>{name = (null), num = 3} 2013-02-03 00:24:16.285 thread[5656:1303] 執行第1次操作,線程:<NSThread: 0x9138b50>{name = (null), num = 4}
可以看出,先執行operation2,再執行operation1
四、修改Operations的執行順序
對於添加到queue中的operations,它們的執行順序取決於2點:
1.首先看看NSOperation是否已經准備好:是否准備好由對象的依賴關系確定
2.然後再根據所有NSOperation的相對優先級來確定。優先級等級則是operation對象本身的一個屬性。默認所有operation都擁有“普通”優先級,不過可以通過setQueuePriority:方法來提升或降低operation對象的優先級。優先級只能應用於相同queue中的operations。如果應用有多個operation queue,每個queue的優先級等級是互相獨立的。因此不同queue中的低優先級操作仍然可能比高優先級操作更早執行。
注意:優先級不能替代依賴關系,優先級只是對已經准備好的 operations確定執行順序。先滿足依賴關系,然後再根據優先級從所有准備好的操作中選擇優先級最高的那個執行。
五、設置隊列的最大並發操作數量
隊列的最大並發操作數量,意思是隊列中最多同時運行幾條線程
雖然NSOperationQueue類設計用於並發執行Operations,你也可以強制單個queue一次只能執行一個Operation。setMaxConcurrentOperationCount:方法可以配置queue的最大並發操作數量。設為1就表示queue每次只能執行一個操作。不過operation執行的順序仍然依賴於其它因素,比如operation是否准備好和operation的優先級等。因此串行化的operation queue並不等同於GCD中的串行dispatch queue
復制代碼 代碼如下:
// 每次只能執行一個操作
queue.maxConcurrentOperationCount = 1;
// 或者這樣寫
[queue setMaxConcurrentOperationCount:1];
六、取消Operations
一旦添加到operation queue,queue就擁有了這個Operation對象並且不能被刪除,唯一能做的事情是取消。你可以調用Operation對象的cancel方法取消單個操作,也可以調用operation queue的cancelAllOperations方法取消當前queue中的所有操作。
復制代碼 代碼如下:
// 取消單個操作
[operation cancel];
// 取消queue中所有的操作
[queue cancelAllOperations];
七、等待Options完成
為了最佳的性能,你應該設計你的應用盡可能地異步操作,讓應用在Operation正在執行時可以去處理其它事情。如果需要在當前線程中處理operation完成後的結果,可以使用NSOperation的waitUntilFinished方法阻塞當前線程,等待operation完成。通常我們應該避免編寫這樣的代碼,阻塞當前線程可能是一種簡便的解決方案,但是它引入了更多的串行代碼,限制了整個應用的並發性,同時也降低了用戶體驗。絕對不要在應用主線程中等待一個Operation,只能在第二或次要線程中等待。阻塞主線程將導致應用無法響應用戶事件,應用也將表現為無響應。
復制代碼 代碼如下:
// 會阻塞當前線程,等到某個operation執行完畢
[operation waitUntilFinished];
除了等待單個Operation完成,你也可以同時等待一個queue中的所有操作,使用NSOperationQueue的waitUntilAllOperationsAreFinished方法。注意:在等待一個 queue時,應用的其它線程仍然可以往queue中添加Operation,因此可能會加長線程的等待時間。
復制代碼 代碼如下:
// 阻塞當前線程,等待queue的所有操作執行完畢
[queue waitUntilAllOperationsAreFinished];
八、暫停和繼續queue
如果你想臨時暫停Operations的執行,可以使用queue的setSuspended:方法暫停queue。不過暫停一個queue不會導致正在執行的operation在任務中途暫停,只是簡單地阻止調度新Operation執行。你可以在響應用戶請求時,暫停一個queue來暫停等待中的任務。稍後根據用戶的請求,可以再次調用setSuspended:方法繼續queue中operation的執行
復制代碼 代碼如下:
// 暫停queue
[queue setSuspended:YES];
// 繼續queue
[queue setSuspended:NO];