Created
November 19, 2012 22:37
-
-
Save taxilian/4114555 to your computer and use it in GitHub Desktop.
Deferred object implementation for Objective C
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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