I'm building a demonstration app and am trying to conform to the ReactiveCocoa design pattern as much as possible. Here's what the app does:
- Find the device's location
- Whenever the location key changes, fetch:
- Current weather
- Hourly forecast
- Daily forecast
So the order is 1) update location 2) merge all 3 weather fetches. I've built a WeatherManager
singleton that exposes weather objects, location information, and methods to manually update. This singleton conforms to the CLLocationManagerDelegate
protocol. The location code is very basic, so I'm leaving it out. The only real point of interest is this:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
// omitting accuracy & cache checking
CLLocation *location = [locations lastObject];
self.currentLocation = location;
[self.locationManager stopUpdatingLocation];
}
Fetching the weather conditions is all very similar, so I've built a method to generate a RACSignal
for fetching JSON from a URL.
- (RACSignal *)fetchJSONFromURL:(NSURL *)url {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (! error) {
NSError *jsonError = nil;
id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
if (! jsonError) {
[subscriber sendNext:json];
}
else {
[subscriber sendError:jsonError];
}
}
else {
[subscriber sendError:error];
}
[subscriber sendCompleted];
}];
[dataTask resume];
return [RACDisposable disposableWithBlock:^{
[dataTask cancel];
}];
}];
}
This helps me keep my methods nice and clean, so now I have 3 short methods that build a URL and return the RACSignal. The nice thing here is I can create side-effects to parse the JSON and assign the appropriate properties (note: I'm using Mantle here).
- (RACSignal *)fetchCurrentConditions {
// build URL
return [[self fetchJSONFromURL:url] doNext:^(NSDictionary *json) {
// simply converts JSON to a Mantle object
self.currentCondition = [MTLJSONAdapter modelOfClass:[CurrentCondition class] fromJSONDictionary:json error:nil];
}];
}
- (RACSignal *)fetchHourlyForecast {
// build URL
return [[self fetchJSONFromURL:url] doNext:^(NSDictionary *json) {
// more work
}];
}
- (RACSignal *)fetchDailyForecast {
// build URL
return [[self fetchJSONFromURL:url] doNext:^(NSDictionary *json) {
// more work
}];
}
Finally, in the -init
of my singleton, I set up the RAC observers on location, since every time the location changes I want to fetch and update the weather.
[[RACObserve(self, currentLocation)
filter:^BOOL(CLLocation *newLocation) {
return newLocation != nil;
}] subscribeNext:^(CLLocation *newLocation) {
[[RACSignal merge:@[[self fetchCurrentConditions], [self fetchDailyForecast], [self fetchHourlyForecast]]] subscribeError:^(NSError *error) {
NSLog(@"%@",error.localizedDescription);
}];
}];
Everything works just fine, but I'm concerned that I'm straying from the Reactive way to structure my fetches and property assignments. I tried doing sequencing with -then:
but wasn't really able to get that setup how I would like.
I was also trying to find a clean way to bind the result of an async fetch to the properties of my singleton, but ran into trouble getting that to work. I wasn't able to figure out how to "extend" the fetching RACSignal
s (note: that's where the -doNext:
idea came from for each of those).
Any help clearing this up or resources would be really great. Thanks!