Skip to content

Instantly share code, notes, and snippets.

@andyshep
Created June 3, 2014 21:27
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andyshep/c8744856f4bf75ba3d05 to your computer and use it in GitHub Desktop.
Save andyshep/c8744856f4bf75ba3d05 to your computer and use it in GitHub Desktop.
A network layer using ReactiveCocoa
#import <Foundation/Foundation.h>
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface PASDataSource : NSObject
@property (nonatomic, strong, readonly) NSArray *passes;
+ (PASDataSource *)sharedInstance;
- (RACSignal *)refreshPassInfoSignal;
@end
#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]];
}
#import <Foundation/Foundation.h>
@interface PASParser : NSObject
+ (PASParser *)parser;
- (NSDictionary *)parsePassFromResponse:(NSData *)response;
@end
#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
#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
#import <Foundation/Foundation.h>
#import <AFNetworking/AFNetworking.h>
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface PASSignaller : NSObject
- (RACSignal *)passInfoForURL:(NSURL *)url info:(NSDictionary *)info;
@end
#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