Skip to content

Instantly share code, notes, and snippets.

@diogot
Last active October 14, 2015 14:44
Show Gist options
  • Save diogot/3f3ba09cefc36f229498 to your computer and use it in GitHub Desktop.
Save diogot/3f3ba09cefc36f229498 to your computer and use it in GitHub Desktop.
//
// DTNetworkOperation.h
// DTOperation
//
// Created by Diogo on 10/14/15.
// Copyright © 2015 Diogo Tridapalli. All rights reserved.
//
#import "DTOperation.h"
/**
* Class to encapsulate a NSURLSessionTask inside a NSOperation
*/
@interface DTNetworkOperation : DTOperation
/**
* Default method to create an operation, the operation should be put on a queue ou
* started manually, it's a good idea to keep a strong reference to the instance
* while the operation is not finished
*
* @param task NSURLSessionTask to be executed
*
* @return NSOperation
*/
+ (instancetype)operationWithTask:(NSURLSessionTask *)task;
@end
//
// DTNetworkOperation.m
// DTOperation
//
// Created by Diogo on 10/14/15.
// Copyright © 2015 Diogo Tridapalli. All rights reserved.
//
#import "DTNetworkOperation.h"
static NSString * const kTaskStateKeyPath = @"state";
static void * DTContext = &DTContext;
@interface DTNetworkOperation ()
@property (nonatomic, readonly) NSURLSessionTask *task;
- (instancetype)initWithTask:(NSURLSessionTask *)task;
@end
@implementation DTNetworkOperation
+ (instancetype)operationWithTask:(NSURLSessionTask *)task
{
return [[self alloc] initWithTask:task];
}
- (instancetype)initWithTask:(NSURLSessionTask *)task
{
if (task.state != NSURLSessionTaskStateSuspended) {
NSLog(@"This should not happen");
return nil;
}
self = [super init];
if (self) {
_task = task;
[_task addObserver:self
forKeyPath:kTaskStateKeyPath
options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew
context:DTContext];
}
return self;
}
- (void)dealloc
{
[_task removeObserver:self forKeyPath:kTaskStateKeyPath context:DTContext];
}
- (void)execute
{
NSURLSessionTask *task = self.task;
if (task.state != NSURLSessionTaskStateSuspended) {
assert(NO);
NSLog(@"This should not happen");
return;
}
[task resume];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
id oldValue = change[NSKeyValueChangeOldKey];
id newValue = change[NSKeyValueChangeNewKey];
if (oldValue && newValue && [newValue isEqual:oldValue]) {
return;
}
if (context == DTContext &&
object == self.task &&
[keyPath isEqualToString:kTaskStateKeyPath] &&
self.task.state == NSURLSessionTaskStateCompleted) {
[self finish];
}
}
- (void)cancel
{
[self.task cancel];
[super cancel];
}
@end
//
// DTOperation.h
// DTOperation
//
// Created by Diogo on 10/14/15.
// Copyright © 2015 Diogo Tridapalli. All rights reserved.
//
@import Foundation;
/**
* Operation states
*/
typedef NS_ENUM(NSInteger, DTOperationState){
/**
* Operation is ready to execute
*/
kDTOperationStateReady = 0,
/**
* Operation is executing
*/
kDTOperationStateExecuting,
/**
* Operation is finished
*/
kDTOperationStateFinished,
/**
* Operation is finished due to cancel
*/
kDTOperationStateCanceled
};
/**
* Class to be subclassed for encapsulate a task in an operation
*/
@interface DTOperation : NSOperation
/**
* Operation state
*
* @see DTOperationState
*/
@property (readonly) DTOperationState state;
/**
* Methdos that must be overide on subclasses, the task should be started inside this method and
* finish shold be called on task completion
*/
- (void)execute;
/**
* When the task is finished this method should be called
*/
- (void)finish;
@end
//
// DTOperation.m
// DTOperation
//
// Created by Diogo on 10/14/15.
// Copyright © 2015 Diogo Tridapalli. All rights reserved.
//
#import "DTOperation.h"
static NSString * const kStateKeyPath = @"state";
static inline BOOL IsValidStateTransition(DTOperationState fromState,
DTOperationState toState)
{
if (fromState == toState) {
NSLog(@"Inconsistent state, from %ld to %ld", (long)fromState, (long)toState);
return NO;
}
switch (fromState) {
case kDTOperationStateReady:
case kDTOperationStateExecuting:
return toState > fromState;
case kDTOperationStateFinished:
case kDTOperationStateCanceled:
default:
return NO;
}
}
@interface DTOperation (){
DTOperationState _state;
}
@property (readwrite) DTOperationState state;
@end
@implementation DTOperation
// use the KVO mechanism to indicate that changes to "state" affect other properties as well
+ (NSSet *)keyPathsForValuesAffectingIsReady
{
return [NSSet setWithObject:kStateKeyPath];
}
+ (NSSet *)keyPathsForValuesAffectingIsExecuting
{
return [NSSet setWithObject:kStateKeyPath];
}
+ (NSSet *)keyPathsForValuesAffectingIsFinished
{
return [NSSet setWithObject:kStateKeyPath];
}
+ (NSSet *)keyPathsForValuesAffectingIsCanceled
{
return [NSSet setWithObject:kStateKeyPath];
}
- (DTOperationState)state
{
@synchronized(self) {
return _state;
}
}
- (void)setState:(DTOperationState)state
{
@synchronized(self) {
if (NO == IsValidStateTransition(_state, state)) {
return;
}
[self willChangeValueForKey:kStateKeyPath];
_state = state;
[self didChangeValueForKey:kStateKeyPath];
}
}
+ (BOOL)automaticallyNotifiesObserversOfState
{
return NO;
}
- (BOOL)isReady
{
return self.state == kDTOperationStateReady && [super isReady];
}
- (BOOL)isExecuting
{
return self.state == kDTOperationStateExecuting;
}
- (BOOL)isFinished
{
return self.state >= kDTOperationStateFinished;
}
- (BOOL)isCancelled
{
return self.state == kDTOperationStateCanceled;
}
- (void)start
{
if (self.state != kDTOperationStateReady) {
return;
}
self.state = kDTOperationStateExecuting;
[self execute];
}
- (void)execute
{
[self finish];
}
- (void)cancel
{
self.state = kDTOperationStateCanceled;
}
- (void)finish
{
self.state = kDTOperationStateFinished;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment