I'm starting out with ReactiveCocoa. The simple things make sense, but I can't yet do complexity. ;)
This is what I'm trying to model: I have a view controller that displays some data requested from an HTTP endpoint. The HTTP endpoint uses a browser-like basic auth with cookies.
So, I want to make that HTTP request. If it succeeds, great, display the data. If it fails with a 401, I need to tell the view to pop up a modal dialog asking for the username/password, and then retry the HTTP request.
In my ViewModel, then, do I have two signals? One that returns the content, and another which tells the view layer that I need the credentials? How do I get the credentials back down to where the HTTP request happens?
Your ViewModel adapts your application's model to its view. In other words, it maintains whatever state that the view needs to display (in the form of bindable properties) and exposes API to update that state and do the "work" of your application (in the form of plain old methods). So, judging from what you wrote:
"I have a view controller that displays some data requested from an HTTP endpoint."
It sounds like to start with, your ViewModel should have some way to represent this data as state. This can almost always be done with Objective-C properties:
@interface MyViewModel : NSObject
@property (nonatomic, strong) NSArray *tableData;
// (Or whatever makes sense for your view.)
@property (nonatomic) BOOL needCredentials;
@end
Your view controller should then bind to these properties so that whenever a property changes (e.g., whenever new data is retrieved, or a 401 error is received), the corresponding UIViews are updated. Notice how your ViewModel's API doesn't even have any ReactiveCocoa code. That's because if your view controller has a reference to the ViewModel object, the view controller can use ReactiveCocoa to bind to the ViewModel in whatever way makes sense. For example, in simpler circumstances you can just use RAC(self, infoView.name) = RACObserve(self, myViewModel.infoViewName);
. In more complex cases, such as implementing a UITableViewDelegate, you would need to implement the UITableViewDataSource methods, but it's the same idea. To display a modal dialog asking for the username and password, you might even use something like
- (void)viewDidLoad
{
self.myViewModel = [[ViewModel alloc] init];
@weakify(self);
[[RACObserve(self, myViewModel.needCredentials) ignore:@NO] subscribeNext:^(id _) {
@strongify(self);
[self displayModalDialog];
}];
}
"So, I want to make that HTTP request. If it succeeds, great, display the data. If it fails with a 401, I need to tell the view to pop up a modal dialog asking for the username/password, and then retry the HTTP request."
Your ViewModel could have a method such as - (void)sendRequest:(NSDictionary *)parameters
. Calling this method from your view controller might look like this:
- (IBAction)handleButtonTap:(id)sender
{
NSDictionary *parameters = [self makeParametersFromTextFields];
[self.myViewModel sendRequest:parameters];
}
Notice again: no ReactiveCocoa code necessary in your ViewModel's API. That's not to say you shouldn't use RAC, only that the API of a ViewModel isn't necessarily dependent on signals or any ReactiveCocoa concepts – it's just a model object that is specifically intended to service a specific view in your application. Within the ViewModel's method implementations, you might be using signals all over the place, or maybe you're using some more imperative API like NSOperationQueues or something. It doesn't really matter, as long as your ViewModel exposes data to the view via KVO-observable properties (so that your view controller can bind to those properties, which would typically be done using ReactiveCocoa).
So what does your -sendRequest:
method do with this dictionary of parameters? I have no idea. That part is up to you. If it gets a valid response, you should update some property on the ViewModel (for example, the tableData
property from the code snippet above). If it gets a 401
, it should update some other property on the ViewModel (for example, set the needCredentials
property to YES
). The view controller, having already bound itself to these properties, will react in whatever way you've configured.
"In my ViewModel, then, do I have two signals? One that returns the content, and another which tells the view layer that I need the credentials? How do I get the credentials back down to where the HTTP request happens?"
I hope by this point I've answered the question. The ViewModel doesn't need any signals. It just needs KVO-able properties. And as demonstrated above in the -handleButtonTap:
method example, you don't need to do anything special to get the credentials down to where the HTTP request happens – just call a method on the ViewModel, and pass in whatever data makes sense. (Of course, the ViewModel would have to know which object to give the credentials to in order to kick off the HTTP request, and handle the response, but that part should be pretty academic.)