前言
GCD是一種很強(qiáng)大的多線程解決方案,但NSOperation同樣也支持多樣性的操作。
NSOperation有三種狀態(tài)
isReady -> isExecution -> isFinish
- isReady: 返回 YES 表示操作已經(jīng)準(zhǔn)備好被執(zhí)行, 如果返回NO則說明還有其他沒有先前的相關(guān)步驟沒有完成。
- isExecuting: 返回YES表示操作正在執(zhí)行,反之則沒在執(zhí)行。
- isFinished : 返回YES表示操作執(zhí)行成功或者被取消了
NSOperationQueue只有當(dāng)它管理的所有操作的isFinished屬性全標(biāo)為YES以后操作才停止出列,也就是隊列停止運行,所以正確實現(xiàn)這個方法對于避免死鎖很關(guān)鍵。
簡單使用NSOperation
NSOperation不可以直接創(chuàng)建,但是我們可以使用它的子類NSBlockOperation
和NSInvocationOperation
,前者是使用Block的方式,使用起來比較方便。
NSOperationQueue使用
類似Java線程池,可以先創(chuàng)建一個線程隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount = 2; //最大并發(fā)數(shù)
或者獲取main隊列
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:operation];
NSBlockOperation 使用
再創(chuàng)建NSInvocationOperation或者NSBlockOperation的字例,添加到NSOperationQueue當(dāng)中,隊列就會依次執(zhí)行線程
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
UIImage *image = [weakSelf doLoadImageWithURLString:@"http:xxx.jpg"];
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
weakSelf.imageView2.image = image;
}];
}];
[queue addOperation:operation2];
[weakSelf nsBlockOperationLoadImage];
}];
也可以直接
[_operationQueue addOperationWithBlock:^{
}];
有時候使直接調(diào)用start
方法,但是這樣子就是使當(dāng)前的線程阻塞。所以我不是不建議這樣子做滴。
NSInvocationOperation
創(chuàng)建NSInvocationOperation
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(nsinvocationOperationLoadImage) object:nil]`
下面這個是加載圖片的方法
-(void)nsinvocationOperationLoadImage{
__weak NSOperationViewController *weakSelf = self;
UIImage *image = [self doLoadImageWithURLString:@"http://e.hiphotos.baidu.com/image/pic/item/c8ea15ce36d3d539228bfe2f3887e950342ab0ac.jpg"];
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
[weakSelf.NSInvocationOperationImageView setImage:image];
}];
}
從上面看來NSOperation是不是比GCD方便很多呢?
NSOperation進(jìn)階
優(yōu)先級
跟NSThread一樣,NSOpertion也可以設(shè)置優(yōu)先級。
@property NSOperationQueuePriority queuePriority;
執(zhí)行順序(依賴)
有些時候想要控制執(zhí)行順序,使用NSOpreation會方便多了,使用NSOpreation的Dependency就可以實現(xiàn)這種功能。
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"excute operation2");
}];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"excute operation1");
}];
[ope0ration2 addDependency:ope0ration1];
[queue addOperation:ope0ration1];
[queue addOperation:ope0ration2];
上面先執(zhí)行第一個operation1,等operation1返回isFinish為YES,即operation1完成了,才會執(zhí)行operation2。
注意死鎖:一定不可以循環(huán)依賴,像A依賴B,B依賴A,一定不要這樣做
CompletionBlock
這個比較容易理解,就是每個NSOperation執(zhí)行完畢之后,就會執(zhí)行該block
NSOperationQueue *queue = [NSOperationQueue mainQueue];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"執(zhí)行操作");
}];
[operation setCompletionBlock:^{
NSLog(@"執(zhí)行操作完成");
}];
[queue addOperation:operation];
執(zhí)行結(jié)果
2015-09-22 23:47:47.640 Thread Learn[21307:662442] 執(zhí)行操作
2015-09-22 23:47:47.640 Thread Learn[21307:662482] 執(zhí)行操作完成
取消
如前面所說,NSOperation有三種狀態(tài),isReady -> isExecuting -> isFinish, 如果在Ready的狀態(tài)中對NSOperation進(jìn)行取消,NSOperation會進(jìn)入Finish狀態(tài)。但是Operation已經(jīng)開始執(zhí)行了,就會一直運行到結(jié)束,或者由我們進(jìn)行顯示取消。也就是說Operation已經(jīng)在executing狀態(tài),我們調(diào)用cancle方法系統(tǒng)不會中止線程的,這需要我們在任務(wù)過程中檢測取消事件,并中止線程的執(zhí)行,還要注意一點我們要釋放內(nèi)存或資源。還是看一下實例代碼:
- (IBAction)startNSOperation:(id)sender {
self.blockOperation = [NSBlockOperation blockOperationWithBlock:^{
if ([self.blockOperation isCancelled]) {
NSLog(@"取消了");
return;
}
//如果檢測還沒取消
//TODO:這里請求網(wǎng)絡(luò),獲取數(shù)據(jù)..
if ([self.blockOperation isCancelled]) {
NSLog(@"取消了");
return;
}
//如果檢測還沒取消
//TODO:獲取到了數(shù)據(jù)刷新界面...
}];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:self.blockOperation];
}
這種取消跟NSThread有點相似,調(diào)用cancle不會退出線程,需要你自已去中止線程,再exit;
自定義NSOperation
如果NSBlockOperation和NSInvocationOperation都不能滿足你應(yīng)用的需求,你可以選擇繼承NSOperation并做你想做的操作。
自定義非并發(fā)
繼承非并發(fā)的Operation比并發(fā)的要容易的多,只需要實現(xiàn)以下兩個方法就行了
- 自定義初始化方法
- main方法
需要自定義初始化方法改變Operation的狀態(tài),而把你的實現(xiàn)代碼放到main方法里。先看一個簡單的例子:
@interface MyNonConcurrentOperation : NSOperation
@property id (strong) myData;
-(id)initWithData:(id)data;
@end
@implementation MyNonConcurrentOperation
- (id)initWithData:(id)data {
if (self = [super init]){
myData = data;
}
return self;
}
-(void)main {
@try {
// Do some work on myData and report the results.
}
@catch(...) {
// Do not rethrow exceptions.
}
}
@
很簡單,上面的代碼提供了一個參數(shù)為data的初始化方法,而你可以在main里面寫上你的代碼。
你還可以從這里下載這份代碼
并發(fā)
自定義并發(fā)的NSOperation就麻煩多了,我們可以看一下下面這個表(來自蘋果官方):
方法 | 描述 |
---|---|
start | (必選)所有的并發(fā)Operation必需重寫這個方法并且要實現(xiàn)這個方法的內(nèi)容來代替原來的操和。手動執(zhí)行一個操作,你可以調(diào)用start方法。因此,這個方法的實現(xiàn)是這個操作的始點,也是其他線程或者運行這你這個任務(wù)的起點。注意一下,在這里永遠(yuǎn)不要調(diào)用[super start]。 |
main | (可選)這個方法就是你的實現(xiàn)的操作(懶得翻譯了,哈哈) |
isExecuting 和 isFinish | (必選)并發(fā)隊列負(fù)責(zé)維持當(dāng)前操作的環(huán)境和告訴外部調(diào)用者當(dāng)前的運行狀態(tài)。因此,一個并發(fā)隊列必需維持保持一些狀態(tài)信息以至于知道什么時候執(zhí)行任務(wù),什么時候完成任務(wù)。它必須通過這些方法告訴外部當(dāng)前的狀態(tài)。這種而且這些方法必須是線程安全,當(dāng)狀態(tài)發(fā)生改變的時候,你必須使用KVO通知監(jiān)聽這些狀態(tài)的對象。 |
isConcurrent | (必選)定義一個并發(fā)操作,重寫這個方法并且返回YES |
話不多說,選看一下例子:
@interface MyOperation : NSOperation {
BOOL executing;
BOOL finished;
}
- (void)completeOperation;
@end
@implementation MyOperation
- (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;
}
@end
上面的代碼簡單實現(xiàn)了isFinish
、isExecuting
、isConcurrent
三個方法,isConcurrent
只需要返回YES就可以了。isFinish
和isExecuting
返回當(dāng)前實例的屬性就可以了。
- (void)start {
[self.lock lock];
// 在開始任務(wù)之前要測試一下是否取消
if ([self isCancelled])
{
// 如果是已經(jīng)取消了,必需要把Finish設(shè)為YES
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
// 如果沒有取消,就繼續(xù)運行代碼
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
[self.lock unlock];
}
- (void)main {
@try {
// 寫你業(yè)務(wù)代碼
[self completeOperation];
}
@catch(...) {
// Do not rethrow exceptions.
}
}
- (void)completeOperation {
[self.lock lock];
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
[self.lock unlock];
}
注意你的實現(xiàn)要發(fā)出合適的KVO通知,因為如果你的NSOperation實現(xiàn)需要用到工作依賴從屬特性,而你的實現(xiàn)里沒有發(fā)出合適的“isFinished”KVO通知,依賴你的NSOperation就無法正常執(zhí)行。NSOperation支持KVO的屬性有:
- isCancelled
- isConcurrent
- isExecuting
- isFinished
- isReady
- dependencies
- queuePriority
- completionBlock
當(dāng)然也不是說所有的KVO通知都需要自己去實現(xiàn),例如通常你用不到addObserver到你工作的“isCancelled”屬性,你只需要直接調(diào)用cancel方法就可以取消這個工作任務(wù)。
參考文章