在iOS開發(fā)中,為了提升用戶體驗,我們通常會將操作耗時的操作放在主線程之外的線程進行處理。對于正常的簡單操作,我們更多的是選擇代碼更少的GCD,讓我們專注于自己的業(yè)務邏輯開發(fā)。NSOperation在ios4后也基于GCD實現(xiàn),但是相對于GCD來說可控性更強,并且可以加入操作依賴。
NSOperation是一個抽象的基類,表示一個獨立的計算單元,可以為子類提供有用且線程安全的建立狀態(tài),優(yōu)先級,依賴和取消等操作。系統(tǒng)已經(jīng)給我們封裝了NSBlockOperation和NSInvocationOperation這兩個實體類。使用起來也非常簡單,不過我們更多的使用是自己繼承并定制自己的操作。
NSOperation定義
- (void)start;
- (void)main;
@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;
@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 NS_AVAILABLE(10_8, 7_0);
@property (readonly, getter=isReady) BOOL ready;
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;
@property (readonly, copy) NSArray *dependencies;
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
@property NSOperationQueuePriority queuePriority;
@property (copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
@property (copy) NSString *name NS_AVAILABLE(10_10, 8_0);
狀態(tài)
NSOperation提供了ready cancelled executing finished這幾個狀態(tài)變化,我們的開發(fā)也是必須處理自己關心的其中的狀態(tài)。這些狀態(tài)都是基于keypath的KVO通知決定,所以在你手動改變自己關心的狀態(tài)時,請別忘了手動發(fā)送通知。這里面每個屬性都是相互獨立的,同時只可能有一個狀態(tài)是YES。finished這個狀態(tài)在操作完成后請及時設置為YES,因為NSOperationQueue所管理的隊列中,只有isFinished為YES時才將其移除隊列,這點在內(nèi)存管理和避免死鎖很關鍵。
依賴
NSOperation中我們可以為操作分解為若干個小的任務,通過添加他們之間的依賴關系進行操作,這點在設計上是很有意義的。比如我們最常用的圖片異步加載,第一步我們是去通過網(wǎng)絡進行加載,第二步我們可能需要對圖片進行下處理(調整大小或者壓縮保存)。我們可以直接調用- (void)addDependency:(NSOperation*)op;這個方法添加依賴:
[imgRsizingOperation addDependency:networkOperation];
[operationQueue addOperation:networkOperation];
[operationQueue addOperation:imgRsizingOperation];
這點我們必須要注意的是不能添加相互依賴,像A依賴B,B依賴A,這樣會導致死鎖!還有一點必須要注意的時候,在每個操作完成時,請將isFinished設置為YES,不然后續(xù)的操作是不會開始執(zhí)行的。
執(zhí)行
執(zhí)行一個operation有兩種方法,第一種是自己手動的調用start這個方法,這種方法調用會在當前調用的線程進行同步執(zhí)行,所以在主線程里面自己一定要小心的調用,不然就會把主線程給卡死,還不如直接用GCD呢。第二種是將operation添加到operationQueue中去,這個也是我們用得最多的也是提倡的方法。NSOperationQueue會在我們添加進去operation的時候盡快進行執(zhí)行。當然如果NSOperationQueue的maxConcurrentOperationCount如果設置為1的話,進相當于FIFO了。
隊列是怎么調用我們的執(zhí)行的操作的呢?如果你只是想弄一個同步的方法,那很簡單,你只要重寫main這個函數(shù),在里面添加你要的操作。如果想定義異步的方法的話就重寫start方法。在你添加進operationQueue中的時候系統(tǒng)將自動調用你這個start方法,這時將不再調用main里面的方法。
取消
NSOperation允許我們調用-(void)cancel取消一個操作的執(zhí)行。當然,這個操作并不是我們所想象的取消。這個取消的步驟是這樣的,如果這個操作在隊列中沒有執(zhí)行,那么這個時候取消并將狀態(tài)finished設置為YES,那么這個時候的取消就是直接取消了。如果這個操作已經(jīng)在執(zhí)行了,那么我們只能等其操作完成。當我們調用cancel方法的時候,他只是將isCancelled設置為YES。所以,在我們的操作中,我們應該在每個操作開始前,或者在每個有意義的實際操作完成后,先檢查下這個屬性是不是已經(jīng)設置為YES。如果是YES,則后面操作都可以不用在執(zhí)行了。
completionBlock
iOS4后添加了這個block,在這個操作完成時,將會調用這個block一次,這樣也非常方便的讓我們對view進行更新或者添加自己的業(yè)務邏輯代碼。
優(yōu)先級
operationQueue有maxConcurrentOperationCount設置,當隊列中operation很多時而你想讓后續(xù)的操作提前被執(zhí)行的時候,你可以為你的operation設置優(yōu)先級
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
簡單示例代碼
最后我們看看一個簡單的小示例,在.m文件里面我們將重寫finished executing兩個屬性。我們重寫set方法,手動發(fā)送keyPath的KVO通知。在start函數(shù)中,我們首先判斷是否已經(jīng)取消,如果取消的話,我們將直接return,并將finished設置為YES。如果沒有取消操作,我們將_executing設置為YES,表示當前operation正在執(zhí)行,繼續(xù)執(zhí)行我們的邏輯代碼。在執(zhí)行完我們的代碼后,別忘了設置operation的狀態(tài),將_executing設置為NO,并將finished設置為YES,這樣我們就已經(jīng)很簡單的完成了我們的多線程操作任務。
@interface TestOperation ()
@property (nonatomic, assign) BOOL finished;
@property (nonatomic, assign) BOOL executing;
@end
@implementation TestOperation
@synthesize finished = _finished;
@synthesize executing = _executing;
- (void)start
{
if ([self isCancelled]) {
_finished = YES;
return;
} else {
_executing = YES;
//start your task;
//end your task
_executing = NO;
_finished = YES;
}
}
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}