建立一個支援非同步作業的operation,建立非同步operation
NSOperationQueue時iOS中常用的任務調度機制。在建立一個複雜任務的時候,我們通常都需要編寫?NSOperation的子類。在大部分情況下,重寫main方法就可以滿足要求。main方法執行完畢後,系統就會認為這個operation完成了。
有時候情況並沒有這麼簡單。我們需要在operation中調用非同步API,這個API會通過一個block或者代理通知我們結果。這時只靠覆蓋main方法就顯得力不從心了。因為非同步API尚未執行完畢,operation就提前結束了。
怎麼解決這個問題呢?我想到AFNetworking中有同樣的案例,於是參考了其中的實現,設計了一個基於非同步任務的operation。
我們需要覆蓋start方法。這個方法的作用有點類似於main方法,在這裡實現任務的代碼邏輯。系統怎麼知道我們的任務開始執行,或者完成了呢?系統會通過KVO的形式,監聽operation的一些屬性。我們可以重新實現這些屬性,來通知系統任務執行的狀態。重要的屬性有:
@property (readonly, getter=isReady) BOOL ready;@property (readonly, getter=isExecuting) BOOL executing;@property (readonly, getter=isFinished) BOOL finished;
它們都是唯讀屬性。我們可以簡單的重寫它們,返回我們想要的值。但是如何通知系統它們的值已經變化了呢?如果你瞭解KVO,那麼你就應該知道NSObject的這一對方法能夠協助我們,可以利用它們手動通知系統某個屬性發生了變化。
- (void)willChangeValueForKey:(NSString *)key;- (void)didChangeValueForKey:(NSString *)key;
下面就是完整的代碼。這裡只用了一個NSTimer類比一個非同步任務。在state變化時,我們需要通知KVO系統operation的狀態發生了變化。這一步很重要,我剛開始忽略了手動通知KVO,導致任務永遠無法完成(即使start中的任務全部執行完畢)。
typedef NS_ENUM(NSInteger, MyOperationState) { MyOperationStateReady, MyOperationStateExecuting, MyOperationStateFinished};@interface MyOperation : NSOperation@property (nonatomic, strong) NSTimer *exeTimer;@property (nonatomic, assign) MyOperationState state;@property (nonatomic, strong) NSLock *lock;@end@implementation MyOperation- (instancetype)init{ self = [super init]; if (self) { self.lock = [NSLock new]; [self willChangeValueForKey:@"isReady"]; self.state = MyOperationStateReady; [self willChangeValueForKey:@"isReady"]; } return self;}- (void)start{ [self.lock lock]; if (!self.finished && self.state == MyOperationStateReady) { [self willChangeValueForKey:@"isExecuting"]; self.state = MyOperationStateExecuting; [self didChangeValueForKey:@"isExecuting"]; self.exeTimer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(finish) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.exeTimer forMode:NSRunLoopCommonModes]; } [self.lock unlock];}- (void)cancel{ [self.lock lock]; if (!self.isFinished && !self.cancelled) { [super cancel]; [self.exeTimer invalidate]; } [self.lock unlock];}- (BOOL)isReady{ return self.state == MyOperationStateReady;}- (BOOL)isExecuting{ return self.state == MyOperationStateExecuting;}- (BOOL)isFinished{ return self.state == MyOperationStateFinished;}- (BOOL)isAsynchronous{ return YES;}- (BOOL)isConcurrent{ return YES;}- (void)finish{ [self.lock lock]; [self willChangeValueForKey:@"isFinished"]; self.state = MyOperationStateFinished; [self didChangeValueForKey:@"isFinished"]; [self.lock unlock];}@end