Created
June 3, 2014 21:27
-
-
Save andyshep/c8744856f4bf75ba3d05 to your computer and use it in GitHub Desktop.
A network layer using ReactiveCocoa
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
#import <Foundation/Foundation.h> | |
#import <ReactiveCocoa/ReactiveCocoa.h> | |
@interface PASDataSource : NSObject | |
@property (nonatomic, strong, readonly) NSArray *passes; | |
+ (PASDataSource *)sharedInstance; | |
- (RACSignal *)refreshPassInfoSignal; | |
@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
#import "PASDataSource.h" | |
#import "PASConstants.h" | |
#import "PASPassDetailCell.h" | |
#import "PASPass.h" | |
#import "PASSignaller.h" | |
#import <EXTScope.h> | |
#import <HexColor.h> | |
@interface PASDataSource () | |
@property (nonatomic, strong, readwrite) NSArray *passes; | |
@property (nonatomic, strong, readwrite) PASSignaller *dataRetriever; | |
@property (nonatomic, strong, readwrite) NSMutableArray *tempPasses; | |
@property (nonatomic, strong) NSDateFormatter *dateFormatter; | |
@end | |
@implementation PASDataSource | |
- (NSDateFormatter *)dateFormatter { | |
if (!_dateFormatter) { | |
_dateFormatter = [[NSDateFormatter alloc] init]; | |
[_dateFormatter setDateStyle:NSDateFormatterMediumStyle]; | |
[_dateFormatter setTimeStyle:NSDateFormatterShortStyle]; | |
} | |
return _dateFormatter; | |
} | |
+ (PASDataSource *)sharedInstance { | |
static PASDataSource *_sharedInstance = nil; | |
static dispatch_once_t onceToken; | |
dispatch_once(&onceToken, ^{ | |
_sharedInstance = [[PASDataSource alloc] init]; | |
}); | |
return _sharedInstance; | |
} | |
- (id)init { | |
if ((self = [super init])) { | |
self.passes = [self initialModel]; | |
self.dataRetriever = [[PASSignaller alloc] init]; | |
[[self refreshPassInfoSignal] subscribeCompleted:^{ | |
[[NSNotificationCenter defaultCenter] postNotificationName:PASPassesDidUpdateNotification object:nil]; | |
}]; | |
} | |
return self; | |
} | |
- (NSDictionary *)seedDictionary { | |
NSDictionary *dictionary = @{@"Snoqualmie": @"http://www.wsdot.com/traffic/passes/snoqualmie/", | |
@"Stevens": @"http://www.wsdot.com/traffic/passes/stevens/", | |
@"White": @"http://www.wsdot.com/traffic/passes/white/", | |
@"Status": @"http://www.wsdot.com/traffic/passes/satus/default.aspx", | |
@"Manastash Ridge": @"http://www.wsdot.com/traffic/passes/manastash/default.aspx", | |
@"Blewett": @"http://www.wsdot.com/traffic/passes/blewett/default.aspx"}; | |
return dictionary; | |
} | |
- (NSArray *)initialModel { | |
NSMutableArray *array = [NSMutableArray array]; | |
[[self seedDictionary] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { | |
PASPass *pass = [[PASPass alloc] init]; | |
[pass setTitle:key]; | |
[pass setReferenceURL:[NSURL URLWithString:obj]]; | |
[pass setUniqueId:[pass.referenceURL hash]]; | |
[array addObject:pass]; | |
}]; | |
return [NSArray arrayWithArray:array]; | |
} | |
- (RACSignal *)refreshPassInfoSignal { | |
NSMutableArray *signals = [NSMutableArray array]; | |
[self.passes enumerateObjectsUsingBlock:^(PASPass *pass, NSUInteger idx, BOOL *stop) { | |
NSDictionary *info = @{@"referenceURL": pass.referenceURL, @"title": pass.title}; | |
RACSignal *signal = [self.dataRetriever passInfoForURL:pass.referenceURL info:info]; | |
[signals addObject:signal]; | |
}]; | |
RACReplaySubject *subject = [RACReplaySubject subject]; | |
@weakify(self); | |
NSMutableArray *passes = [NSMutableArray array]; | |
[[RACSignal merge:signals] subscribeNext:^(PASPass *pass) { | |
NSAssert([pass isKindOfClass:[PASPass class]], @"Wrong class type"); | |
[passes addObject:pass]; | |
} completed:^{ | |
@strongify(self); | |
self.passes = [NSArray arrayWithArray:passes]; | |
[subject sendCompleted]; | |
}]; | |
return [subject deliverOn:[RACScheduler mainThreadScheduler]]; | |
} |
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
#import <Foundation/Foundation.h> | |
@interface PASParser : NSObject | |
+ (PASParser *)parser; | |
- (NSDictionary *)parsePassFromResponse:(NSData *)response; | |
@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
#import "PASParser.h" | |
#import <TFHpple.h> | |
@implementation PASParser | |
+ (PASParser *)parser { | |
return [[self alloc] init]; | |
} | |
- (id)init { | |
if ((self = [super init])) { | |
// | |
} | |
return self; | |
} | |
- (NSDictionary *)parsePassFromResponse:(NSData *)response { | |
TFHpple *parser = [TFHpple hppleWithHTMLData:response]; | |
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; | |
[[self queryDictionary] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { | |
NSString *result = [self contentAtXPath:obj usingParser:parser]; | |
if (result) { | |
[dictionary setObject:result forKey:key]; | |
} else { | |
[dictionary setObject:[NSNull null] forKey:key]; | |
} | |
}]; | |
return [NSDictionary dictionaryWithDictionary:dictionary]; | |
} | |
- (NSString *)contentAtXPath:(NSString *)xpath usingParser:(TFHpple *)parser { | |
NSString *content = nil; | |
NSArray *results = [parser searchWithXPathQuery:xpath]; | |
if (results.count > 0) { | |
content = [[[results firstObject] firstChild] content]; | |
} | |
return content; | |
} | |
- (NSDictionary *)queryDictionary { | |
NSDictionary *queryDictionary = @{@"conditions": @"//span[@id='PassInfoConditions']", | |
@"eastbound": @"//span[@id='PassInfoRestrictionsOne']", | |
@"westbound": @"//span[@id='PassInfoRestrictionsTwo']", | |
@"last_updated": @"//span[@id='PassInfoLastUpdate']"}; | |
return queryDictionary; | |
} | |
@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
#import <Foundation/Foundation.h> | |
#import <Mantle.h> | |
NS_ENUM(NSUInteger, PASPassStatusType) { | |
PASPassStatusOpen = 1001, | |
PASPassStatusRestricted, | |
PASPassStatusClosed | |
}; | |
@interface PASPass : MTLModel <MTLJSONSerializing> | |
@property (nonatomic, copy) NSString *title; | |
@property (nonatomic, copy) NSString *conditions; | |
@property (nonatomic, copy) NSString *eastbound; | |
@property (nonatomic, copy) NSString *westbound; | |
@property (nonatomic, copy) NSURL *referenceURL; | |
@property (nonatomic, copy) NSDate *lastUpdated; | |
@property (nonatomic) NSUInteger uniqueId; | |
- (BOOL)isOpen; | |
- (BOOL)isClosed; | |
@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
#import <Foundation/Foundation.h> | |
#import <AFNetworking/AFNetworking.h> | |
#import <ReactiveCocoa/ReactiveCocoa.h> | |
@interface PASSignaller : NSObject | |
- (RACSignal *)passInfoForURL:(NSURL *)url info:(NSDictionary *)info; | |
@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
#import "PASSignaller.h" | |
#import "PASParser.h" | |
#import "PASPass.h" | |
@interface PASSignaller () | |
- (RACSignal *)signalForURLRequest:(NSURLRequest *)request; | |
@end | |
@implementation PASSignaller | |
- (instancetype)init { | |
if ((self = [super init])) { | |
// | |
} | |
return self; | |
} | |
- (RACSignal *)passInfoForURL:(NSURL *)url info:(NSDictionary *)info { | |
NSURLRequest *request = [NSURLRequest requestWithURL:url]; | |
RACSignal *signal = [[self signalForURLRequest:request] map:^id(id responseObj) { | |
PASParser *parser = [[PASParser alloc] init]; | |
NSDictionary *parsedResponse = [parser parsePassFromResponse:responseObj]; | |
NSMutableDictionary *passObj = [NSMutableDictionary dictionaryWithDictionary:parsedResponse]; | |
PASPass *pass = [MTLJSONAdapter modelOfClass:[PASPass class] fromJSONDictionary:passObj error:nil]; | |
[pass setUniqueId:[pass.referenceURL hash]]; | |
[pass setConditions:pass.conditions]; | |
[pass setTitle:pass.title]; | |
[info enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { | |
[pass setValue:obj forKey:key]; | |
}]; | |
return pass; | |
}]; | |
return signal; | |
} | |
#pragma mark - Private | |
- (RACSignal *)signalForURLRequest:(NSURLRequest *)request { | |
RACReplaySubject *subject = [RACReplaySubject subject]; | |
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; | |
[operation setResponseSerializer:[AFHTTPResponseSerializer serializer]]; | |
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { | |
[subject sendNext:responseObject]; | |
[subject sendCompleted]; | |
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { | |
[subject sendError:error]; | |
[subject sendCompleted]; | |
}]; | |
[[NSOperationQueue mainQueue] addOperation:operation]; | |
return [subject deliverOn:[RACScheduler scheduler]]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment