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.
Related
I want to implement a webservice client in iOS which uses SOAP and XML for requests/responses.
My view starts the initial businnes logic (a user presses a button or something and initiates some businnes method called method_A).
So I have a class with method_A and this method checks if the user is logged in etc and then starts the request asynchronous via the SOAPConnector-class. So the UI is not blocked (asynchronous).
The SOAPConnector-class takes the XML and handles the requests. I use therefore NSURLRequest and NSURLConnection with sendSynchronousRequest.
The response is sended back to a Response-class which takes the response. This class then wants to parse the response XML. Therefore I use an extra class called XMLManager which uses NSXMLParser to parse the xml. But again here we need a delegate which gets the parsed xml. And again after parsing I have to implement an extra method to give back the parsed xml to the first class which initiated the request.
I am really wondering if this is the right way. The first problem is asnychronous request to not block the UI (the first callback). The second problem is the parsing where I am forced to use the delegate (the second callback). This results in a lot of classes and methods and I doubt this is the right way. The classes' only purpose is to manage the delegate and async problems. So I am asking for any suggestions and help how to solve this. Do you know some good design patterns to solve this problem?
Apart from some inconsistencies in the way you describe the design patterns you've selected:
and then starts the request asynchronous
vs.
I use therefore NSURLRequest and NSURLConnection with
sendSynchronousRequest.
That said, your approach seems sound. Addressing the issues you've identified:
I use therefore NSURLRequest and NSURLConnection with
sendSynchronousRequest.
Isn't that the purpose of using an asynchronous API? If your NSURLConnection is really operating asynchronously, that issue should be covered.
The second problem is the parsing where I am forced to use the
delegate
This approach does result in more classes, delegation, etc. but it conforms to best practices when it comes to testing. If you are performing unit testing or other testing strategies (you are aren't you?) then testing in isolation is all the harder unless you breakdown this process functionally.
If you have access to the book Test-Drive iOS Development there is a great section on the best practices for consuming web services with a view toward testability.
The Problem
I have a class which calculates the path a user took using CoreLocation and and array of arrays containing the coordinates of each point (taken when the users location changes). This class method is being called by my View Controller, but I want to set it's delegate to another class which will store the result in Core Data or upload it to a database. I can return the array to the View Controller by using:
PathFinder.delegate = self
Then make my View Controller implement my delegate protocol, but this isn't what I want.
What I've Considered
I've thought about making the class which uploads the data to the database/stores it in Core Data a singleton class so that I can easily access it from my View Controller. E.g.
PathFinder.delegate = <MY SINGLETON CLASS>
Conclusion
What would be the best way to do this? Would it be bad practice to put the code to upload the array to my server in the PathFinder class? Any help would be appreciated.
I have something like this - a singleton class that manages a Core Data repository for images (some in the repository, some on the file system but a URL in the entity).
Why not have a singleton class that all objects that need the services import? That way, you tell some object to do something, when that works is done they tell the repository to save it. You can use a delegate protocol to know if it succeeded or not, but just decouple the saving of it from driving the process and knowing the outcome.
I am using CoreData in my application. I want to abstract out all the CoreData related stuff as an API so that the consume can use the API instead of directly using CoreData and its generated model objects. CoreData generates the managed objects model as following
#interface Person : NSManagedObject
#end
I want to define my API for example MyAPI and it has a function called as
createPerson:(Person*)p;
So the consumer of this createPerson API needs to create a Person data object (like POJO in java world) and invoke this API. But I cannot create Person object using
Person *p = [Person alloc] init]
because the designated initializer for this Person model created by CoreData does not allow this type of creation.
So should I define corresponding user facing data object may be PersonDO and this API should take that instead to carry the data into the API implementation?
Is my approach right? Any expert advise if design the API this way is a good design pattern?
You can't use CoreData without dealing with Managed Object Context. You MUST get the person from the context and save it in the context. If you don't want your customer to deal with that, and you don't use different threads in your app,you could create a api,that will call your appdelegate's Managed Object Context, and all the user will need to do is just to call that method.
For example,you could use automaticly generated by Core Data methods to make your API like that:
+ (Person *)newPersonWithAttributes:(NSDictionary *)userInfo {
// Call the designated initializer, but customer
// doesn't need to think about managed object context
return [Person initPesonWithAttributes:userInfo
inManagedObjectContext:[[AppDelegate sharedAppDelegate] managedObjectContext]];
}
You need lots of stuff to abstract that out. RestKit provides mechanisms to do that. Lighter weight would be InnerBand (See tutorial InnerBand CoreData) - though it does not automatically determine threads/queues for multi-threads.
However, it is a very simple API, and should give you most of what you want.
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.
When using async download methods and delegates, is it possible to start multiple simultaneous nsconnections and handle them separately as each query completes? Or, will the system not automatically distinguish between them as your delegates receive pieces from each query? In which case, what would be a good approach to identify each connection uniquely as it enters through the delegates?
The system won't automatically distinguish the NSURLConnections, instead, each delegate methods called is precising the concerned connection.
For example : - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
the connection is connection. This way you can set several NSURLConnections and react accordingly.
I personnaly set NSURLConnection objects as ivars and test for equality in delegate methods, because using delegate, you will need to access the data containers from different methods.
You will find more information in the excellent URL Loading System Programming Guide from Apple.