Skip to content

Instantly share code, notes, and snippets.

@taxilian
Created November 19, 2012 22:37
Show Gist options
  • Save taxilian/4114555 to your computer and use it in GitHub Desktop.
Save taxilian/4114555 to your computer and use it in GitHub Desktop.
Deferred object implementation for Objective C
//
// Deferred.h
// Insight
//
// Created by Richard Bateman on 11/19/12.
// Copyright (c) 2012 Richard Bateman. All rights reserved.
//
#import <Foundation/Foundation.h>
@class Deferred;
typedef void(^StdDfdCallback)(NSArray *args);
typedef id(^PipeDfdCallback)(NSArray *args);
@interface Deferred : NSObject
// General Deferred helpers
+ (Deferred*) whenArray:(NSArray*)inArr;
+ (Deferred*) when:(Deferred*)dfd, ...;
+ (Deferred*) pipe:(Deferred*)dfd forSuccessTo:(PipeDfdCallback)pipeDoneCB forFailureTo:(PipeDfdCallback)pipeFailCB;
+ (Deferred*) pipe:(Deferred*)dfd forSuccessTo:(PipeDfdCallback)pipeDoneCB;
+ (Deferred*) pipe:(Deferred*)dfd forFailureTo:(PipeDfdCallback)pipeFailCB;
// Shortcut helpers for pipe
+ (Deferred*) DeferredResolvedWith:(NSArray*)args;
+ (Deferred*) DeferredRejectedWith:(NSArray*)args;
// Basic callback setters
- (void) onDoneCall:(StdDfdCallback)cb;
- (void) onFailCall:(StdDfdCallback)cb;
- (void) alwaysCall:(StdDfdCallback)cb;
// Pipe methods
- (Deferred*) pipeDoneTo:(PipeDfdCallback)pipeDoneCB pipeFailTo:(PipeDfdCallback)pipeFailCB;
- (Deferred*) pipeDoneTo:(PipeDfdCallback)pipeDoneCB;
- (Deferred*) pipeFailTo:(PipeDfdCallback)pipeFailCB;
// Resolution and state methods
- (void) resolveWith:(NSArray *)args;
- (void) rejectWith:(NSArray *)args;
- (NSString*) state;
@end
//
// Deferred.m
// Insight
//
// Created by Richard Bateman on 11/19/12.
// Copyright (c) 2012 Richard Bateman. All rights reserved.
//
#import "Deferred.h"
enum DFD_STATE {
DFD_STATE_PENDING = 0,
DFD_STATE_RESOLVED,
DFD_STATE_REJECTED
};
@interface Deferred() {
NSMutableArray* doneCallbacks;
NSMutableArray* failCallbacks;
NSArray *stateVars;
enum DFD_STATE state;
}
@end
@implementation Deferred
+ (Deferred*) whenArray:(NSArray*)inArr {
if (!inArr.count) {
return [Deferred DeferredResolvedWith:nil];
} else if (inArr.count == 1) {
id item = inArr[0];
if ([item isKindOfClass:[Deferred class]]) {
return item;
} else {
return [Deferred DeferredResolvedWith:inArr];
}
}
Deferred* newDfd = [Deferred new];
__block int resolved = 0;
__block int rejected = 0;
__block int count = 0;
__block BOOL done = NO;
NSMutableArray* arr = [NSMutableArray arrayWithArray:inArr];
void(^checkIfDone)() = ^{
if (resolved == count) {
done = YES;
[newDfd resolveWith:arr];
}
};
void(^reject)() = ^{
if (!done) {
done = YES;
[newDfd rejectWith:arr];
}
};
[arr enumerateObjectsUsingBlock:^(id item, NSUInteger i, BOOL *stop) {
if ([item isKindOfClass:[Deferred class]]) {
// For a deferred object, add the handlers
Deferred* dfdCur = item;
[dfdCur onDoneCall:^(NSArray *args) {
if (done) { return; }
++resolved;
if (args.count == 1) {
arr[i] = args[0];
} else {
arr[i] = args;
}
checkIfDone();
}];
[dfdCur onFailCall:^(NSArray *args) {
if (done) { return; }
++rejected;
if (args.count == 1) {
arr[i] = args[0];
} else {
arr[i] = args;
}
reject();
}];
} else {
++resolved;
arr[i] = item;
}
}];
checkIfDone();
return newDfd;
}
+ (Deferred*) when:(Deferred*)dfd, ... {
va_list params;
__block NSMutableArray* arr = [[NSMutableArray alloc] init];
if (!dfd) { // nil deferred; resolve immediately
return [Deferred DeferredResolvedWith:nil];
}
// Build the list of deferred objects
[arr addObject:dfd];
va_start(params, dfd);
id cur;
while ((cur = va_arg(params, id))) {
[arr addObject:cur];
}
va_end(params);
return [Deferred whenArray:arr];
}
+ (Deferred*) pipe:(Deferred*)dfd forSuccessTo:(PipeDfdCallback)pipeDoneCB forFailureTo:(PipeDfdCallback)pipeFailCB {
Deferred* newDfd = [Deferred new];
if (pipeDoneCB) {
[dfd onDoneCall:^(NSArray *args) {
id res = pipeDoneCB(args);
if (!res) {
// Allow 'nil' instead of an empty NSArray
[newDfd resolveWith:nil];
} else if ([res isKindOfClass:[Deferred class]]) {
// If the result is another deferred object then we need to resolve that
// and evaluate it
Deferred* innerDfd = res;
[innerDfd onDoneCall:^(NSArray *args) {
[newDfd resolveWith:args];
}];
[innerDfd onFailCall:^(NSArray *args) {
[newDfd rejectWith:args];
}];
} else if ([res isKindOfClass:[NSArray class]]) {
// If an NSArray is returned then use that as the list of params
[newDfd resolveWith:res];
} else if ([res isKindOfClass:[NSError class]]) {
// If an NSError is returned, reject it
[newDfd rejectWith:@[res]];
} else {
// If anything else is returned, put it as a sole argument in the NSArray
[newDfd resolveWith:@[res]];
}
}];
} else {
[dfd onDoneCall:^(NSArray *args) {
[newDfd resolveWith:args];
}];
}
if (pipeFailCB) {
[dfd onFailCall:^(NSArray *args) {
id res = pipeFailCB(args);
if (!res) {
// Allow 'nil' instead of an empty NSArray
[newDfd resolveWith:nil];
} else if ([res isKindOfClass:[Deferred class]]) {
// If the result is another deferred object then we need to resolve that
// and evaluate it
Deferred* innerDfd = res;
[innerDfd onDoneCall:^(NSArray *args) {
[newDfd resolveWith:args];
}];
[innerDfd onFailCall:^(NSArray *args) {
[newDfd rejectWith:args];
}];
} else if ([res isKindOfClass:[NSArray class]]) {
// If an NSArray is returned then use that as the list of params and resolve
// (Note that using a pipe on failure by default transforms it to a success)
[newDfd resolveWith:res];
} else if ([res isKindOfClass:[NSError class]]) {
// If an NSError is returned, reject it
[newDfd rejectWith:@[res]];
} else {
// If anything else is returned, put it as a sole argument in the NSArray
// (Note that using a pipe on failure by default transforms it to a success)
[newDfd resolveWith:@[res]];
}
}];
} else {
[dfd onFailCall:^(NSArray *args) {
[newDfd rejectWith:args];
}];
}
return newDfd;
}
+ (Deferred*) pipe:(Deferred*)dfd forSuccessTo:(PipeDfdCallback)pipeDoneCB {
return [Deferred pipe:dfd forSuccessTo:pipeDoneCB forFailureTo:nil];
}
+ (Deferred*) pipe:(Deferred*)dfd forFailureTo:(PipeDfdCallback)pipeFailCB {
return [Deferred pipe:dfd forSuccessTo:nil forFailureTo:pipeFailCB];
}
+ (Deferred*) DeferredResolvedWith:(NSArray*)args {
Deferred* dfd = [Deferred new];
[dfd resolveWith:args];
return dfd;
}
+ (Deferred*) DeferredRejectedWith:(NSArray*)args {
Deferred* dfd = [Deferred new];
[dfd rejectWith:args];
return dfd;
}
- (id) init {
if (self = [super init]) {
state = DFD_STATE_PENDING;
stateVars = nil;
doneCallbacks = [[NSMutableArray alloc] init];
failCallbacks = [[NSMutableArray alloc] init];
}
return self;
}
- (void) onDoneCall:(StdDfdCallback)cb {
if (state == DFD_STATE_RESOLVED) {
cb(stateVars);
} else {
[doneCallbacks addObject:cb];
}
}
- (void) onFailCall:(StdDfdCallback)cb {
if (state == DFD_STATE_REJECTED) {
cb(stateVars);
} else {
[failCallbacks addObject:cb];
}
}
- (void) alwaysCall:(StdDfdCallback)cb {
[self onDoneCall:cb];
[self onFailCall:cb];
}
- (Deferred*) pipeDoneTo:(PipeDfdCallback)pipeDoneCB {
return [Deferred pipe:self forSuccessTo:pipeDoneCB forFailureTo:nil];
}
- (Deferred*) pipeDoneTo:(PipeDfdCallback)pipeDoneCB pipeFailTo:(PipeDfdCallback)pipeFailCB {
return [Deferred pipe:self forSuccessTo:pipeDoneCB forFailureTo:pipeFailCB];
}
- (Deferred*) pipeFailTo:(PipeDfdCallback)pipeFailCB {
return [Deferred pipe:self forSuccessTo:nil forFailureTo:pipeFailCB];
}
- (void) resolveWith:(NSArray *)args {
if (state != DFD_STATE_PENDING) {
// Do nothing; it's already resolved or rejected
return;
}
stateVars = args;
state = DFD_STATE_RESOLVED;
for (StdDfdCallback cb in doneCallbacks) {
cb(args);
}
}
- (void) rejectWith:(NSArray *)args {
if (state != DFD_STATE_PENDING) {
// Do nothing; it's already resolved or rejected
return;
}
stateVars = args;
state = DFD_STATE_REJECTED;
for (StdDfdCallback cb in failCallbacks) {
cb(args);
}
}
- (NSString*) state {
switch(state) {
case DFD_STATE_PENDING:
return @"pending";
case DFD_STATE_REJECTED:
return @"rejected";
case DFD_STATE_RESOLVED:
return @"resolved";
default:
return @"Unknown";
}
}
@end
//
// DeferredHttpRequest.h
// Insight
//
// Created by Richard Bateman on 11/20/12.
// Copyright (c) 2012 Richard Bateman. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Deferred.h"
@interface DeferredHttpRequest : NSObject
+ (Deferred*)sendRequest:(NSURLRequest*)request;
@end
//
// DeferredHttpRequest.m
// Insight
//
// Created by Richard Bateman on 11/20/12.
// Copyright (c) 2012 Richard Bateman. All rights reserved.
//
#import "DeferredHttpRequest.h"
#import "Deferred.h"
@implementation DeferredHttpRequest
+ (Deferred*)sendRequest:(NSURLRequest*)request {
Deferred* dfd = [Deferred new];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse* resp, NSData* data, NSError* err) {
NSHTTPURLResponse* httpResp = (NSHTTPURLResponse*)resp;
if (!httpResp) {
[dfd rejectWith:@[[NSNumber numberWithInteger:-1], err]];
} else if (httpResp.statusCode >= 400) {
[dfd rejectWith:@[[NSNumber numberWithInteger:httpResp.statusCode], data, httpResp]];
} else {
[dfd resolveWith:@[data, httpResp]];
}
}];
return dfd;
}
@end
// note that this file is probably overkill...
//
// GCAlertView.h
// Insight
//
// Created by Richard Bateman on 11/20/12.
// Copyright (c) 2012 Richard Bateman. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Deferred.h"
@interface GCAlertView : NSObject
- (id)initWithTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString*)cancelBtn;
- (Deferred*) showDfd;
- (UIAlertViewStyle) alertViewStyle;
- (void) setAlertViewStyle:(UIAlertViewStyle)style;
- (NSInteger) cancelButtonIndex;
- (void) setCancelButtonIndex:(NSInteger)idx;
- (NSInteger) firstOtherButtonIndex;
- (NSString*) message;
- (void) setMessage:(NSString*)msg;
- (NSString *) title;
- (void) setTitle: (NSString*)title;
- (NSInteger) numberOfButtons;
- (BOOL) visible;
- (NSInteger)addButtonWithTitle:(NSString *)title;
- (NSString *)buttonTitleAtIndex:(NSInteger)buttonIndex;
- (void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated;
- (void)show;
- (UITextField *)textFieldAtIndex:(NSInteger)textFieldIndex;
@end
//
// GCAlertView.m
// Insight
//
// Created by Richard Bateman on 11/20/12.
// Copyright (c) 2012 Richard Bateman. All rights reserved.
//
#import "GCAlertView.h"
#import "Deferred.h"
#include <UIKit/UIKit.h>
@interface GCAlertView() {
UIAlertView* alertView;
NSString* clickedButton;
Deferred* dfd;
}
@end
@implementation GCAlertView
- (id)initWithTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString*)cancelBtn {
if (self = [super init]) {
alertView = [[UIAlertView alloc] initWithTitle:title message:message delegate:self cancelButtonTitle:cancelBtn otherButtonTitles:nil];
clickedButton = nil;
dfd = nil;
}
return self;
}
- (void)alertView:(UIAlertView *)aView clickedButtonAtIndex:(NSInteger)buttonIndex {
clickedButton = [alertView buttonTitleAtIndex:buttonIndex];
[dfd resolveWith:[NSArray arrayWithObject:clickedButton]];
dfd = nil;
}
- (void)alertViewCancel:(UIAlertView *)aView {
[dfd rejectWith:nil];
dfd = nil;
}
- (Deferred*) showDfd {
dfd = [Deferred new];
[alertView show];
return dfd;
}
- (UIAlertViewStyle) alertViewStyle { return alertView.alertViewStyle; }
- (void) setAlertViewStyle:(UIAlertViewStyle)style { alertView.alertViewStyle = style; }
- (NSInteger) cancelButtonIndex { return alertView.cancelButtonIndex; }
- (void) setCancelButtonIndex:(NSInteger)idx { alertView.cancelButtonIndex = idx; }
- (NSInteger) firstOtherButtonIndex { return alertView.firstOtherButtonIndex; }
- (NSString*) message { return alertView.message; }
- (void) setMessage:(NSString*)msg { alertView.message = msg; }
- (NSString *) title { return alertView.title; }
- (void) setTitle: (NSString*)title { alertView.title = title; }
- (NSInteger) numberOfButtons { return alertView.numberOfButtons; }
- (BOOL) visible { return alertView.visible; }
- (NSInteger)addButtonWithTitle:(NSString *)title { return [alertView addButtonWithTitle:title]; }
- (NSString *)buttonTitleAtIndex:(NSInteger)buttonIndex { return [alertView buttonTitleAtIndex:buttonIndex]; }
- (void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated {
[alertView dismissWithClickedButtonIndex:buttonIndex animated:animated];
}
- (void)show { [alertView show]; }
- (UITextField *)textFieldAtIndex:(NSInteger)textFieldIndex { return [alertView textFieldAtIndex:textFieldIndex]; }
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment