Skip to content

Instantly share code, notes, and snippets.

@farktronix
Created June 4, 2014 18:04
Show Gist options
  • Save farktronix/92c3b1a1901eec6c6f2e to your computer and use it in GitHub Desktop.
Save farktronix/92c3b1a1901eec6c6f2e to your computer and use it in GitHub Desktop.
dispatch_group based state machine
//
// StateMachineExample
//
// Created by Jacob Farkas on 6/4/14.
//
#import <Foundation/Foundation.h>
#define DownloadTempPath "/tmp/tacosXXXXXX"
typedef NS_ENUM(NSUInteger, MyOperationState) {
MyOperationStateNotStarted = 1,
MyOperationStateDownloadingPages,
MyOperationStateProcessingData,
MyOperationStateComplete = UINT_MAX,
};
@interface FindNewlinesOperation : NSOperation
- (instancetype)initWithURLsToDownload:(NSArray *)urlsToDownload;
@property (nonatomic, strong) NSArray /* NSURL* */ *urlsToDownload;
@property (nonatomic, copy) void (^pagesFetchedBlock)(NSUInteger newlineCount);
@end
@interface FindNewlinesOperation ()
@property (nonatomic, assign) MyOperationState state;
@property (nonatomic, strong) dispatch_group_t stateTransitionGroup;
@property (nonatomic, assign) BOOL isFinished;
@property (nonatomic, assign) BOOL isExecuting;
@property (nonatomic, strong) NSMutableArray *downloadTasks;
@property (nonatomic, strong) NSMutableArray /* NSFileHandle */ *downloadedFiles;
@property (atomic, assign) NSUInteger newlineCount;
@end
@implementation FindNewlinesOperation
- (instancetype)initWithURLsToDownload:(NSArray *)urlsToDownload {
if ((self = [super init])) {
_state = MyOperationStateNotStarted;
_stateTransitionGroup = dispatch_group_create();
_urlsToDownload = urlsToDownload;
_downloadTasks = [NSMutableArray new];
_downloadedFiles = [NSMutableArray new];
}
return self;
}
- (void)setIsFinished:(BOOL)isFinished {
[self willChangeValueForKey:@"isFinished"];
_isFinished = isFinished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setIsExecuting:(BOOL)isExecuting {
[self willChangeValueForKey:@"isExecuting"];
_isExecuting = isExecuting;
[self didChangeValueForKey:@"isExecuting"];
}
// Automatically advances the state machine to the next state when everything has left the dispatch group
// This only needs to be called once from the start method.
- (void)makeStateTransition {
switch(self.state) {
case MyOperationStateNotStarted:
self.state = MyOperationStateDownloadingPages;
[self downloadPages];
break;
case MyOperationStateDownloadingPages:
self.state = MyOperationStateProcessingData;
[self processData];
break;
case MyOperationStateProcessingData:
case MyOperationStateComplete:
if (self.pagesFetchedBlock) self.pagesFetchedBlock(self.newlineCount);
self.isExecuting = NO;
self.isFinished = YES;
break;
}
if (!self.isFinished) {
dispatch_group_notify(self.stateTransitionGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self makeStateTransition];
});
}
}
- (void)start {
self.isFinished = NO;
self.isExecuting = YES;
[self makeStateTransition];
}
- (void)main {
[self start];
}
- (void)downloadPages {
for (NSURL *url in self.urlsToDownload) {
dispatch_group_enter(self.stateTransitionGroup);
NSURLSessionDownloadTask *downloadTask = [[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (location) {
int fd = open([location fileSystemRepresentation], O_RDONLY);
if (fd >= 0) {
NSFileHandle *fh = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
@synchronized(self.downloadedFiles) {
[self.downloadedFiles addObject:fh];
}
}
}
dispatch_group_leave(self.stateTransitionGroup);
}];
[self.downloadTasks addObject:downloadTask];
[downloadTask resume];
}
}
- (void)processData {
for (NSFileHandle *fh in self.downloadedFiles) {
dispatch_group_enter(self.stateTransitionGroup);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *fileData = [fh readDataOfLength:getpagesize()];
while ([fileData length]) {
for (char *c = (char *)[fileData bytes]; c < (char *)[fileData bytes] + [fileData length]; c++) {
if (*c == '\n') self.newlineCount++;
}
fileData = [fh readDataOfLength:getpagesize()];
}
dispatch_group_leave(self.stateTransitionGroup);
});
}
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *urlsToDownload = [NSMutableArray new];
NSArray *args = [[NSProcessInfo processInfo] arguments];
for (NSString *urlString in [args subarrayWithRange:NSMakeRange(1, [args count] - 1)]) {
@try {
NSURL *curURL = [NSURL URLWithString:urlString];
if (curURL) {
[urlsToDownload addObject:curURL];
}
} @catch (NSException *e) { }
}
FindNewlinesOperation *op = [[FindNewlinesOperation alloc] initWithURLsToDownload:urlsToDownload];
op.pagesFetchedBlock = ^(NSUInteger newlineCount) {
NSLog(@"%ld newlines were found on the URLs you provided", newlineCount);
};
op.completionBlock = ^{
exit(0);
};
[[NSOperationQueue mainQueue] addOperation:op];
dispatch_main();
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment