Files
RestKit/Code/Support/RKOperationStateMachine.m

204 lines
8.0 KiB
Objective-C

//
// RKOperationStateMachine.m
// RestKit
//
// Created by Blake Watters on 4/11/13.
// Copyright (c) 2013 RestKit. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "TransitionKit.h"
#import "RKOperationStateMachine.h"
NSString *const RKOperationFailureException = @"RKOperationFailureException";
static NSString *const RKOperationStateReady = @"Ready";
static NSString *const RKOperationStateExecuting = @"Executing";
static NSString *const RKOperationStateFinished = @"Finished";
static NSString *const RKOperationEventStart = @"start";
static NSString *const RKOperationEventFinish = @"finish";
static NSString *const RKOperationLockName = @"org.restkit.operation.lock";
@interface RKOperationStateMachine ()
@property (nonatomic, strong) TKStateMachine *stateMachine;
@property (nonatomic, weak, readwrite) NSOperation *operation;
@property (nonatomic, assign, readwrite) dispatch_queue_t operationQueue;
@property (nonatomic, assign, getter = isCancelled) BOOL cancelled;
@property (nonatomic, copy) void (^cancellationBlock)(void);
@property (nonatomic, strong) NSRecursiveLock *lock;
@end
@implementation RKOperationStateMachine
- (id)initWithOperation:(NSOperation *)operation queue:(dispatch_queue_t)operationQueue
{
if (! operation) [NSException raise:NSInvalidArgumentException format:@"Invalid argument: `operation` cannot be nil."];
if (! operationQueue) [NSException raise:NSInvalidArgumentException format:@"Invalid argument: `operationQueue` cannot be nil."];
self = [super init];
if (self) {
self.operation = operation;
self.operationQueue = operationQueue;
self.stateMachine = [TKStateMachine new];
self.lock = [NSRecursiveLock new];
[self.lock setName:RKOperationLockName];
TKState *readyState = [TKState stateWithName:RKOperationStateReady];
[readyState setWillExitStateBlock:^(TKState *state, TKStateMachine *stateMachine) {
[self.operation willChangeValueForKey:@"isReady"];
}];
[readyState setDidExitStateBlock:^(TKState *state, TKStateMachine *stateMachine) {
[self.operation didChangeValueForKey:@"isReady"];
}];
TKState *executingState = [TKState stateWithName:RKOperationStateExecuting];
[executingState setWillEnterStateBlock:^(TKState *state, TKStateMachine *stateMachine) {
[self.operation willChangeValueForKey:@"isExecuting"];
}];
[executingState setDidEnterStateBlock:^(TKState *state, TKStateMachine *stateMachine) {
[self.operation didChangeValueForKey:@"isExecuting"];
}];
[executingState setWillExitStateBlock:^(TKState *state, TKStateMachine *stateMachine) {
[self.operation willChangeValueForKey:@"isExecuting"];
}];
[executingState setDidExitStateBlock:^(TKState *state, TKStateMachine *stateMachine) {
[self.operation didChangeValueForKey:@"isExecuting"];
}];
[executingState setDidEnterStateBlock:^(TKState *state, TKStateMachine *stateMachine) {
[NSException raise:NSInternalInconsistencyException format:@"You must configure an execution block via `setExecutionBlock:`."];
}];
TKState *finishedState = [TKState stateWithName:RKOperationStateFinished];
[finishedState setWillEnterStateBlock:^(TKState *state, TKStateMachine *stateMachine) {
[self.operation willChangeValueForKey:@"isFinished"];
}];
[finishedState setDidEnterStateBlock:^(TKState *state, TKStateMachine *stateMachine) {
[self.operation didChangeValueForKey:@"isFinished"];
}];
[finishedState setWillExitStateBlock:^(TKState *state, TKStateMachine *stateMachine) {
[self.operation willChangeValueForKey:@"isFinished"];
}];
[finishedState setDidExitStateBlock:^(TKState *state, TKStateMachine *stateMachine) {
[self.operation didChangeValueForKey:@"isFinished"];
}];
[self.stateMachine addStates:@[ readyState, executingState, finishedState ]];
TKEvent *startEvent = [TKEvent eventWithName:RKOperationEventStart transitioningFromStates:@[ readyState ] toState:executingState];
TKEvent *finishEvent = [TKEvent eventWithName:RKOperationEventFinish transitioningFromStates:@[ executingState ] toState:finishedState];
[self.stateMachine addEvents:@[ startEvent, finishEvent ]];
self.stateMachine.initialState = readyState;
[self.stateMachine activate];
}
return self;
}
- (id)init
{
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"%@ Failed to call designated initializer. Invoke initWithOperation: instead.",
NSStringFromClass([self class])]
userInfo:nil];
}
- (BOOL)isReady
{
return [self.stateMachine isInState:RKOperationStateReady];
}
- (BOOL)isExecuting
{
return [self.stateMachine isInState:RKOperationStateExecuting];
}
- (BOOL)isFinished
{
return [self.stateMachine isInState:RKOperationStateFinished];
}
- (void)start
{
if (! self.operationQueue) [NSException raise:NSInternalInconsistencyException format:@"You must configure an `operationQueue`."];
[self.lock lock];
NSError *error = nil;
BOOL success = [self.stateMachine fireEvent:RKOperationEventStart error:&error];
if (! success) [NSException raise:RKOperationFailureException format:@"The operation unexpected failed to start due to an error: %@", error];
[self.lock unlock];
}
- (void)finish
{
// Ensure that we are finished from the operation queue
dispatch_async(self.operationQueue, ^{
[self.lock lock];
NSError *error = nil;
BOOL success = [self.stateMachine fireEvent:RKOperationEventFinish error:&error];
if (! success) [NSException raise:RKOperationFailureException format:@"The operation unexpected failed to finish due to an error: %@", error];
[self.lock unlock];
});
}
- (void)cancel
{
if ([self isCancelled]) return;
[self.lock lock];
self.cancelled = YES;
[self.lock unlock];
if (self.cancellationBlock) {
dispatch_async(self.operationQueue, ^{
[self.lock lock];
self.cancellationBlock();
[self.lock unlock];
});
}
}
- (void)setExecutionBlock:(void (^)(void))block
{
TKState *executingState = [self.stateMachine stateNamed:RKOperationStateExecuting];
[executingState setDidEnterStateBlock:^(TKState *state, TKStateMachine *stateMachine) {
dispatch_async(self.operationQueue, ^{
[self.lock lock];
block();
[self.lock unlock];
});
}];
}
- (void)setFinalizationBlock:(void (^)(void))block
{
TKState *finishedState = [self.stateMachine stateNamed:RKOperationStateFinished];
[finishedState setWillEnterStateBlock:^(TKState *state, TKStateMachine *stateMachine) {
[self.lock lock];
// Must emit KVO as we are replacing the block configured in `initWithOperation:queue:`
[self.operation willChangeValueForKey:@"isFinished"];
block();
[self.lock unlock];
}];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p (for %@:%p), state: %@, cancelled: %@>",
[self class], self,
[self.operation class], self.operation,
self.stateMachine.currentState.name,
([self isCancelled] ? @"YES" : @"NO")];
}
@end