mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-06-13 01:28:56 +08:00
204 lines
8.0 KiB
Objective-C
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
|