My app currently has what I'm pretty sure is a bad design, but I'm not sure how I can organize it any better. For example: LoginViewController sends the input text for the username and password to an instance of UserController. UserController sends a request to my server to validate the username and password, via NetworkRequestController which is responsible for dealing with all client-server communications. NetworkRequestController parses the server JSON responses and sends it back to a designated selector on the UserController, which then does all of it's business before informing the view of the result.
Currently, the UserController is instantiated as a local property to the LoginViewController during the LoginViewController's viewDidLoad method - though I feel I would prefer it to be a singleton class. The problem is that I need to set another object (such as LoginViewController) as the delegate on UserController in order to get the login result all the way back to the view with the appropriate selectors.
//LoginViewController sends request to local UserController instance
[userController validateLoginWithDelegate:self
withResult:#selector(loginResult:)
forEmail:email.text
forPassword:password.text];
//UserController asks NetworkRequestController to send login to server
- (void)validateLoginWithDelegate:(id)delegateObj
withResult:(SEL)onResult
forEmail:(NSString *)email
forPassword:(NSString *)password {
if(delegateObj != nil && onResult != nil){
self.delegate = delegateObj;
self.resultSelector = onResult;
}
NSString *url = [NSString stringWithFormat:#"/UserService.cfc?method=Authenticate&email=%#&password=%#&returnFormat=JSON", email, password];
[[NetworkRequestController sharedNetworkRequestController]
requestJSONWithURLString:url
andDelegate:self
onFinished:#selector(onLoginResult:)
onFailed:nil
onOffline:nil
progressView:nil
timeout:30];
}
//NetworkRequestController sends the request to the server and sends the parsed JSON back to UserController
[delegate performSelector:onFinished withObject:parsedJSON];
//UserController does it's business and sends the result to the view
if(delegate != nil && resultSelector != nil){
[delegate performSelector:resultSelector withObject:result];
}
I've only been working with Obj-C and Cocoa a few months and I'm pretty sure this is almost considered good, but I'm more certain there's something I'm missing. Can this be achieved with protocols? Advice?
Delegates are intended for two way communication between objects that serve different roles. Usually one object knows how to do something and the other object, the delegate, knows what to do.
When you have one way communication, notifications are often a better choice. You do not need the formality of a delegate with one way communication.
The two can be used together. For example, UIApplication has a delegate but also sends notifications for many of the delegate calls.
If the UserController is a singleton, then any instance can make requests and it can broadcast a notification of the results to all interested objects.
[NSNotificationCenter defaultCenter] sends notifications within your application.
Related
I'll first of all point out that i need advice and not code samples here. I know how to do the code...
I'm in a project where i feel the need to refactor a singleton class with (in my opinion) excessive use of delegations.
The state is that there are two classes, a singleton class "Manager" and another class "BackendManager". These two do most of the entire app's client-server communication as well as a some application logic.
The application is built up of a login, a menu, and some different functions. All these functions are completely independent, one could be a google map showing some business locations and another could be a calendar showing which meetings the user has. The only connection between these to functions is the login itself. This tells me for sure i need to take the code in the "Manager" and "BackendManager" classes related to the map and put in one place and the stuff for the calendar and put another place. And the same for all the other functions. Furthermore take the login and menu functionality and put somewhere else.
The thing i'm unsure about is whether or not to keep the delegation pattern for all these methods or replace some of it with notifications. The reason is that it's currently a big mess. The "Manager" has a "ManagerDelegate" protocol. It's obvious that this protocol has just gotten bigger and bigger in time. First it defines some login methods, for example
- (void)loginSuccess;
then it defines some maps functions
- (void)backendManagerDidLoadLocationTypes:(NSArray*)locationTypes;
- (void)backendManagerDidLoadLocations:(NSArray*)locations;
- (void)backendManagerDidLoadUserLocatios:(CLLocation*)location;
- (void)backendManagerDidLoadSearchResults:(NSArray*)results;
...
then some calendar functions
- (void)backendManagerDidLoadEvents:(NSArray*)events
- (void)backendManagerDidLoadEventTypes:(NSArray*)eventTypes
...
this of course goes on for all functions developed for the menu, as mentioned these are all independent of eachother.
You can imagine the "BackendManager" is just a convenience class for doing the actual fetching of data for rest services. It is done so the "BackendManager" roughly defines all the same methods as the "Manager", for example for the login functionality:
*Manager*
-(void)userLogin:(NSString*)username pin:(NSString*)pin
-(void)backendUserLogInSuccesCallBack:(BackendManager *)controller
*BackendManager*
-(void)userLogIn:(NSString*)username pin:(NSString*)pin
//Same for all other functions in both classes
So the "Manager" just calls the "BackendManager" and then the "BackendManager" will callback the "Manager" which will call the actual controller from where the call originated, for example LoginViewController. This makes the "Manager" superflous and just an annoying class to always go though when trying to understand the code.
Anyway, after walking through all this logic heres what i plan to do:
Build a model class for every function in the app.
LoginModel
MenuModel
MapModel
CalendarModel
...
These will all be singletons and i'll probably make a class to help doing the service-calls, but parsing of the response and other logic will be in the Models themselves for now.
So the question is - does this sound like the right approach, and am i right in thinking that it would be correct take the calls from "Manager" to the controllers, which are currently done using delegation and change them to use notification pattern (NSNotificationCenter), the calls corresponding to these would now be done in for example the LoginModel where is would throw a notification when logged in. The LoginViewController would then be listening for this notification.
The change to notifications is mostly based on the MVC model teached in the Stanford lessons, here i interpret that the delegation pattern is mostly used when communicating between Controllers and Views, and notifications of KVO is used when communicating from the Model to the controller.
This means that now, instead of sending userdata back from the "Manager" to the controller like
- (void)backendManagerDidLoadLocationTypes:(NSArray*)locationTypes;
above, I will throw a notification when they have been fetched and the controller will then fetch the locationtypes from the model using a different call. This would give me two calls in the Model, one to "update" the locationTypes (from server) and one to "get" them (locally). So maybe these types of calls are the ones where i should stick to the delegation pattern? Even though i'd rather use delegation only between views and controllers.
First off, this question is more suited for programmers.stackexchange.com, but I'll give you a quick answer here.
I like the approach of using notifications application level events. UserDidLogin and LocationDidChange are good examples. These events signal a change that all view controllers will want to know about.
I would avoid singletons. Usually view controllers can exchange data in -prepareForSegue:sender: or however you create your child view controllers.
As far as notifications for completed network events with update and get methods: I would avoid this. Always ask for the data using some async method (I prefer completion blocks), let the model take care of when to use a cached copy and when to update from the server.
I would also recommend splitting the control of access to model data from the model. People sometimes label this as a manger pattern, a factory pattern, or a data source pattern, but it all amounts to the same thing. Have a separate object which knows how to fetch data for one particle model. That object will return back fully formed model data.
For example, have a locationTypeDataSource object. The view controller requests location type data from this data source. The data source thens tells the view controller when the data is ready. This could be reported as a notification, KVO, or completion block.
Notification:
[self addObserverForName:LocationTypeDidLoad object:self.locationTypeDataSource queue:nil usingBlock:^(NSNotification *note) {
[self.tableView refreshData]; // or whatever
}
[self.locationTypeDataSource fetchLocationTypes];
KVO:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"locationTypeDataSource.locationTypes"]) {
[self.tableView refreshData]; // or whatever
}
}
[self addObserver:self forKeyPath:#"locationTypeDataSource.locationTypes" options:0 context:NULL];
[self.locationTypeDataSource fetchLocationTypes];
Completion Block:
[self.locationTypesDataSource fetchLocationTypesWithCompletion:^(NSArray *locationTypes, NSError *error){
[self.tableView refreshData]; // or whatever
}];
The key is to use a separate object to fetch and prepare model data.
This question is about wrapping RestKit requests in custom objects, and whether or not I can guarantee that an object sending the RestKit request will also be used as a delegate when the response is returned.
I'm working with an abstruse REST API (Salesforce), and RestKit doesn't appear to handle some of Salesforce's peculiarities well -- for example, when you make a call to the Salesforce REST API, you might get back a partial dataset, and then be given a "nextRecordsUrl" that you must follow to get the next chunk. Others working with Salesforce and REST have run into the same issue (ref https://groups.google.com/forum/#!msg/restkit/HJNjB7I6WVM/8u5n7nHoJQUJ). I created a class to wrap the calls to Salesforce and automatically follow these links, and process the returned data sets correctly. The class instance registers itself as the delegate for the REST call.
The class I wrote works great on its own (when there's a single instance), but I'll be creating other classes and have several instances that will need to handle their data independently. I've read through the documentation and code (it's been a learning experience, I've only worked with ios for about a month now), but I can't see if it's guaranteed that a particular instance will respond as the delegate for its requests alone, or if it might accidentally pick up the responses for another instance.
For example, suppose I have instances A and B, both of which are making requests using RKObjectLoader, and which set themselves as delegates using loader.delegate = self;. Both are running simultaneously (asynchronous). Here's a potential flow I see:
A's RKObjectLoader makes call, registers self as delegate for response
RestKit framework sends request for A
B's RKObjectLoader makes call, registers self as delegate for response
RestKit framework sends request for B
... wait ...
RestKit framework gets response, and somehow determines that this response was from B's request, calling B as the delegate
B delegate methods get called
RestKit framework gets response, and somehow determines that this response was from A's request, calling A as the delegate
A delegate methods get called
I couldn't see how the RestKit framework would distinguish between responses from requests, and thereby ensure that the correct delegate instances get called. In other words, it appears to me that RestKit might get a response to a request, and then call any delegate it wants ... but that seems counterintuitive, and the framework author appears to be very sharp.
So, my questions:
If I have a class instance using RKObjectLoader and setting itself as the delegate, can I be guaranteed that it will be called when the response for that request comes in?
Can you point me to the actual implementation in the RestKit framework that handles this, so I can learn how it's done?
Thanks very much!
I haven't received an answer to this question, but in case somebody has the same question and comes across this, here's the answer: yes, RestKit does appear to match the correct delegate with the outgoing request.
I created a small class (with an (NSString*) identifier property that calls a service, and in the delegates I printed out the instance's identifier with the result. I confirmed that instance A's delegate method was handling instance A's call, and B's was handling B's, with several simultaneous calls.
I am using Distributed Objects (DO) in Objective-C. I have a "server" object that I have vended on the network. Other objects on the network have a proxy to my server object and can thus call methods on the server object. However, can I determine any information about the objects that are calling methods on the server object? That is, I have many "client" objects that can call the server and I would like to distinguish these objects. Also, can I determine other attributes about these objects, e.g., host name, unique identifier?
I had a similar problem. I found that a possible way to identify clients is to have them pass some kind of token object through to the server as part of each call. On the server you can do:
NSConnection* clientConnection = [passedTokenObject connectionForProxy];
This will get you a handle on the connection, which will be unique to each client. Whether or not you can get the information you need depends on what Apple allows you to do with that connection object.
In my application, I had clients first do a "registration" call that I used to gather the information I needed about them.
The other thing that might be useful is to become NSConnectionDelegate for the NSConnection that you use to vend your server object. This will give you access to these methods:
- (BOOL)connection:(NSConnection *)parentConnection shouldMakeNewConnection:(NSConnection *)newConnnection {
// You can inspect new connection being established here and maybe glean info about the client
return YES;
}
- (BOOL) connection:(NSConnection *)c handleRequest:(NSDistantObjectRequest*)doReq {
// You get to see every method that is invoked here and can maybe glean info that you need.
// Returning NO means you're just snooping on the call and it will be handled in the normal way.
return NO;
}
The available "tools" aren't very sufficient I have found and I needed to rework my vended API to help provide the information I needed.
I found that the most effective solution to this problem is to explicitly pass a reference to the calling object as, say, the first parameter of the method. That way, the calling object can be easily identified and even called back if necessary. The resulting argument is of type NSDistantObject *.
OK, I am building an application that will be using ASIHttpRequest in several places to either get JSON data from a web service or will be posting to a web service. I have it working, but really don't like having several instantiations of the request object sprinkled around the different view controllers (Login view controller, a tableview controller, and a detail view controller. I would like to move all my ASIHttpRequest methods to one class and just get back a dictionary of values that came back from the web service. The problem with this is that the delegate methods don't return that I need to have returned.
What would be some suggestions on how I can consolidate all the methods that will create an HTTPRequest and return values when the request is finished.
I can post code if need be, but this is really more of a general question and not a specific issue. If there are any tutorials on how this should be done, I would appreciate it. I want my code to be efficient and pretty :)
Thanks!
Your problem is going to be asynchronousity. I'm not sure what you mean by consolidate, but you can have a singleton (you can just use your app delegate) to call the requests. So you would do something like this:
[(MyAppDelegateClass *)[UIApplication sharedApplication].delegate doLoginStuff:(NSDictionary *)params delegate:self];
If you're doing all this asynchronously, you can't just call this method and have it return something. You'll be stuck with having some sort of callback to your view controller, which is the way ASI works out of the box essentially. At best, you can minimize the code to generate the request and set any repetitive properties.
I'm not sure what you mean by having the instantiations floating throughout. If it's memory you're worried about, it would be no different reusing the same object. ASI typically uses an autoreleased request, so it would be the same as creating it in a "consolidated" method. If it's just clean code, I would do a singleton way and maybe make a shortcut c-based method call in some type of Utilities class that you create and import in with your prefix file. But again, you need the callback methods in your view controller so it doesn't get too much cleaner than it already is meant to work. I share your pain though.
When any request comes back, I almost always have a method that parses the results. Typically I'm only working with one type of web service, so have a method that parses the results and (also logs it to NSLog for debugging purposes) also gives me back an NSDictionary or NSArray.
I am using ASIHTTPRequest in an Objective-C Mac application. I am creating a class that accesses a web service's public API (via ASIHTTPRequest), let's call it PublicAPI. What I am wanting to do is write methods that directly return data from the web service, something like this...
publicAPI = [[PublicAPI alloc] initWithAccountInfo:email:[self email] password:[self password]];
NSArray *theData = [publicAPI getData];
So, getData needs to initiate the ASIHTTPRequest AND return the response from the server. The problem is that I want to make the ASIHTTPRequest calls with ASIHTTPRequest's startAsynchronous option, which delegates the server's response to a requestFinished method.
How can I use the result of requestFinished as the return value of the getData method?
Thanks for your advice!
Your approach would require that getData wait until the response came back before returning which would make the connection synchronous. This does not really make sense.
The delegate pattern that is already implemented by ASIHTTPRequest (and NSURLConnection for that matter) is the best approach here.
If you just need to make a synchronous connection for testing, NSURLConnection has a class method for that:
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
Make your
[publicAPI getData]
actually be a subclass of ASIHTTPRequest. That will receive the ASI delegate's callbacks (success,fail) and that will process (JSON, XML, CoreData, whatever) your incoming data.
You could use any type of notification, after the data is processed, to get the NSArray data back to where you want it.
If you subclass for each of your backend's API calls, with a common system of processing each for your API, you can make communicating with your backend nice and easy.