I am new to iOS development, so I would appreciate some feedback.
I am trying to build an iOS client for my web service. So far this is what I have done:
I am implementing two views (Utility-based app using Storyboard). In the main view, I use a text field and a search button, where the user can enter a query and then click the search button. Once the search button is clicked, my intention is to read the value of the text field, and use it in my Restful call to my web service. My web service replies back with a JSON file with the query results, which I parse and show to the secondary view's text area.
I know how to do the restful call in iOS and how to do the JSON parsing as well as displaying the results on the screen (at least the text stuff, but that's another different question). But my intention is to learn and implement MVC basics to my application.
According to MVC, the controller updates the view, and the model sends out a notification broadcast which the controller can listen to and know if there are any changes in the object. So this is what I would ideally like to do:
My Model - My model would handle the core RESTful call, get the JSON reply, parse it and get the resulting values that I want to display on the view.
My Controller - I would like my controller to listen to my model and obtain the resulting values from Model and display them on View.
Using a quick and dirty way, I can implement the RESTful call, JSON parsing and displaying resulting values - all inside the Controller, but with this technique, if my view changes tomorrow, then I have to re-write my code. Or if I want to add new features, then I have to change my controller. So ideally I would like to have a core Model that's not aware of how View looks like, and just let's the Controller take the results from Model and display them on View.
From what I have read from Google search results so far, two ways of doing this is by a) Key Value Observation and b) Notification center.
For last 2 days, I am trying to find a good decent way to implement Notification center or read more about it, I am not getting a good lead. Some of the questions I have is, can I send out the String results value using Notification center that my controller picks up? How does Notification Center really work with string values? Where can I find some good examples?
So any help regarding this will be very much appreciated.
Some of the questions I have is, can I send out the String results
value using Notification center that my controller picks up?
Yes, that would commonly done using the userInfo property of a NSNotification. userInfo is a plain NSDictionary that may contain instances of NSObject derived objects indexed by keys that are adhering to the NSCopying protocol (commonly NSString is used). Note that the dictionary (userInfo) will retain your parameter object/s.
How does Notification Center really work with string values?
Well, that depends on how you want it to work. But nitpicking aside, see below.
Where can I find some good examples?
Maybe this one helps...
Example
The receiver (controller) registers for the notification:
- (void)registerForNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(modelObjectUpdatedString:)
name:#"StringUpdated"
object:nil];
}
The sender (model) notifies the world:
- (void)stringUpdateWith:(NSString *)theString
{
self.string = theString;
[[[NSNotificationCenter defaultCenter] postNotificationName:#"StringUpdated"
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:self.string, #"String", nil]];
}
The receiver (controller) receives the notification within its handler:
- (void)modelObjectUpdatedString:(NSNotification *)notification
{
ModelObject *postingObject = [notification object];
NSString *string = [[notification userInfo]
objectForKey:#"String"];
...
}
You're thinking along the right path, but still not entirely. As Till "points out" in his comment, you should not handle the RESTful communication inside your model. If I were you, I would create a utility class responsible for fetching the information, and then a class responsible for holding the data(this last class is your model).
It would be clever to create a class method that allocates and initiates a new instance of this object, created from the JSON data fetched through your RESTful communicator class.
From your controller point of view:
RESTHelper *rest = [RESTHelper restHelperWithURL:yourRESTURL];
YourModel *model = [YourModel modelWithJSON:[rest fetchObjectWithID:1]];
// Present your models data in the view.
You may benefit from using CoreData here, and I strongly encourage you to look into that.
Related
I am developing an app that fetches data from the web and displays it to the user. Assume that the data is reviews of a restaurant and one review is displayed on one view. The user can swipe left or right to go to the prev/next review. The data is fetched asynchronously (one thread for each review).
Here is the problem statement - Assume that 5 reviews have been fetched and the user is looking at the 3rd one currently. Now, the 6th review is fetched and I want to display it as the 4th review to the user (because the publish date of the 6th review is more recent than the 5th review). How should my model class inform the view controller?
I have considered some options -
Provide an array to the view controller and then send NSNotifications about new items to be inserted in-between the array at a specific index
Use an NSFetchedResultsController (this is a bit tricky because I am not using it with a table view controller)
View controller always asks for the next review to be displayed (from the model) and does not have a array of reviews with it
Are there any established design patterns that are employed in such a scenario? Other suggestions apart from the 3 above are welcome!
Just use an NSFetchedResultsController. When using NSIndexPaths just ignore the section. It's basically a glorified NSArray with free notifications.
Here's how I think I'd do it:
Make sure that the NSFetchRequest for your NSFetchedResultsController is sorted by publish date.
Handle NSFetchedResultsControllerDelegate methods.
When the NSFetchedResultsController updates, save the current object, reload the collection view, and then scroll to the saved object without any animation. This will appear to the user as if nothing happened to the current page.
While there is no perfect design pattern for every programming problem, the closest I can think of that relates to your problem is a combination of the Command and Observer patterns.
https://en.wikipedia.org/wiki/Command_pattern
The observer pattern is used in the NSNotification center.
While it's unclear as to why you'd want to skip a review, you could have two arrays to store them when fetched. The first holds all reviews that you have fetched. The second holds all reviews that are displayed.
Then you can get the last review in the fetched array, as if it were a stack. This way you always have the last one loaded displayed to the user.
I am confused why the order of display is different than the true order, ie why the 6th review comes before the 5th, but you asked about patterns to help.
Apart from MVC and observer, which are in the other answers and comments, I'd suggest using lazy loading with a virtual proxy. When reviews have been fetched, you can just display their proxy (eg with a "loading..." Message until they're fully in memory).
See more here: http://en.wikipedia.org/wiki/Proxy_pattern
I would recommend using the observing pattern to inform your controller than new data as been fetched. When receiving the signal, your view controller could update its array of "restaurant review" (either by adding the old one and reordering it according to some sort descriptors of your flavor or by querying the DAO directly).
Let's say you are fetching your data from internet and populating a CoreData entity with the results. Once you got your downloaded data you can populate your core data "Review" entity.
In order to "listen" at the change happening in core data, your controller should, in the viewDidLoad body, register itself as an observer for the NSManagedObjectContextDidSaveNotification.
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(updateInfo:) name:NSManagedObjectContextDidSaveNotification object:nil];
Then in your updateInfo, you can get the changes
- (void) updateInfo:(NSNotification *)notification
{
self.reviews = [self.managedObjectContext performRequest:myFetchRequest error:nil];
}
My app depends on some prerequisite data that needs to be pulled remotely. The data retrieved is required by several child view controllers of my tabbarcontroller. I want to pass the retrieved data to these view controllers properties.
I know I can do the following:
UITabBarController *tabBarController =
(UITabBarController *)self.window.rootViewController;
UINavigationController *navigationController =
[tabBarController viewControllers][0];
MyViewController *myViewController =[navigationController viewControllers][0];
myViewController.prop = myVal;
My question is how I can handle this with the delay of a network call. If I do this synchronously it will hold up the user. If I pass the data asynchronously, I don't know if this being executed in the app delegate will respect the delay.
How could I carry out this workflow?
Is there an iOS best practices way to accomplish this use case?
You arrange to get a callback when the data is downloaded. When you get it, you do (on the main thread) whatever you want to do. For example, here's how I download and parse an RSS feed when my app launches:
- (void)doRefresh:(id)sender {
FeedFetcher* ff = [FeedFetcher new];
ff.delegate = self;
[ff downloadAndParseRSS];
}
Now we are downloading and parsing in a background thread, so the user is not blocked. When it's all over, I am the delegate, so I get the callback:
- (void) downloadAndParseRSSSucceeded:(BOOL) ok
receivedData:(NSMutableData*) receivedData
parsedData:(FPFeed*) parsedData
error:(NSError*)err {
// ... get onto main thread, do something with the data
}
If "do something with the data" involves getting a reference to another view controller and handing it some data, fine, no problem.
If, for example, you use the built-in NSURLSession stuff to do your "network call", all of that is handled perfectly coherently for you with lovely delegate callbacks (which can be on the main thread).
My question is how I can handle this with the delay of a network call. If I do this synchronously it will hold up the user.
A good way to handle these things is to make sure that you have a clearly defined data model, and that all the view controllers that need access to the data in question have a reference to the model. Then you can have a background task request the data and update the model as the new data comes in. When the model is updated, post a notification that will tell any interested objects (in this case, your view controllers) that the model has changed.
To make things even more automatic, each view controller can use Key-Value Observing to observe the parts of the model that it cares about, so that it only gets notified when things that affect it change.
Consider something like the following example:
I have a library with books, books have a difficulty and genre.
Each book is an instance of some object and could be presented by a view controller (I.E. a collection view, where each cell is a book, of perhaps a detail view where only one book is displayed).
The data of these books can be updated in the background by some kind of synchronisation method. It is possible only one book is updated, or perhaps one genre.
I would like the classes (mostly the views) to receive a notification of updates. I would like these notifications to be pretty clear. So when all non-fiction books are updated this is what should be notified.
I could of course use separate notification names for each kind, but if we are talking about an entire library, a big collection view containing thousands of objects would mean registering too many observers. In this case the observer would perhaps choose to receive any notification on books, of maybe any of a genre.
What I am missing (or can't seem to figure out) in NSNotification is some kind of granularity to specify this need.
So in short:
Is there a way to tell NSNotification more specifically what kind of notifications I would like to receive/who to send it to?
Alternatively, can I attach an object to a notification? If so, I could model scope (like meta-data) of the notification in this object and let the receiver check this data.
Yes , you can do that, just specify your object and pack your meta-data in an dictionary and attach your notification as userInfo.and use this method to post notification :
- (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo
then you can access your notification like :
- (void)handleNotification:(NSNotification *)noti{
NSDictionary *userInfo = [noti userInfo];
YourObject *object = [noti object];
}
I am currently working on a single view app that does the following:
Grabs users location using CoreLocation
Sends their lon/lat to an API
Returns tide data as JSON
Dive in to objects and keys within the JSON to return: tide levels, locations, etc and display them via text labels.
I have the majority of this working, but it is more so crammed in to viewDidLoad - I am trying to see the best way to organize something like this up. Would I break it in to different methods such as:
setTideData
displayTideData
Or would it be more broken down than that? (And yes im sure it all depends on the details too). I would be displaying probably 8-10 different stats on the view.
Things like
On viewDidLoad do I grab the users location or do it prior on viewDidAppear, etc
Would I then call my displayTideData method inside of viewDidLoad
Just looking for some type of general best practice, was trying to scan for some items but nothing was what I was looking for.
EDIT: Here is an example of my returned data -- https://gist.github.com/ryancoughlin/8043604
Any ideas?
Thanks
This is pretty much a matter of preference and style, but you could simplify your logic in a number of ways by separating certain parts into their own classes, eg:
You could use something like JSON model to deserialize the data into a class, and access it in a more convenient way
Loading the data and deserializing could be handled in a different class that would only return the object with the data you need
Etc.
It's hard to give a better advice without reviewing your code directly, but this should give you some pointers how to simplify things.
EDIT: A bit of code example how to separate the the deserialization and data handling into a custom object:
- (id)initWithJSON:(NSDictionary *)JSON
{
self = [super init];
if (self) {
self.gender = [JSON objectForKey:#"gender"];
self.email = [JSON objectForKey:#"email"];
self.firstName = [JSON objectForKey:#"first_name"];
self.lastName = [JSON objectForKey:#"last_name"];
self.personId = [[JSON objectForKey:#"person_id"] integerValue];
}
return self;
}
You would then simply create an object with the data like this:
Person *person = [[Person alloc] initWithJSON:JSON];
and simply access whatever data you need in a cleaner way:
self.myLabel.text = [NSString stringWithFormat:#"Name: %#", person.firstName];
It sounds like you have a Massive View Controller, which is a bad thing. Try to reorganise your code so you have a fat model and a skinny controller. Controllers should only be responsible for creating and refreshing the view, bootstrapping the model and updating the model based on user interaction. Some possible model objects:
Fetching data from the server.
Parser for converting JSON objects into typed objects (thus encapsulating the knowledge of the JSON structure).
A simple interface (e.g. one object) for your view controller to pull data from and push data to.
Your model should contain all the business logic for the app.
I hope this question isn't too general/ambiguous...
I'm writing an iphone quiz game app and am having trouble figuring out the best way to handle data. Currently I am thinking of having a single Model class that holds an array of "User" classes which each have an array of user-specific "Question" classes. I'd like to be able to access the overarching Model from any of my view controllers, but that means I'll probably have to pass the model object to any new view controller, use a singleton, or do something else. What is the best way to access my Model object from other classes? Another factor I'm not sure about is being able to save the data - would I have to use Core Data/SQLite to save my single Model object, or is there a simpler way?
I'd start by designing a schema using CoreData. IMO, its best to start out using CoreData because then you'll never have to convert your data layer to CoreData, in the event that your app scales beyond a simple object or two.
The other route would be to create a web service that returns your data... so you just call the service and it returns a collection of user objects. You can either send down the entire object graph with the questions, or create another service to return a collection of questions for a specific user. If you have a web server handy, this method scales the best because you don't have to rely on app updates to get new questions into your system. I would still use CoreData to cache the results... so that way you're not downloading the same information all the time.
So when it comes to accessing CoreData objects, I use a repository class that's a singleton. This makes it easy for any view controller to grab an instance of the repository and get some data. Here's what something like that might look like;
[[Repository defaultRepository] findFirst:[User class]
where:#"name == 'John'"]
There's a lot of redundant code to fetch data so wrapping that up in an object will help get all that nasty code, like predicates and sorting, out of your view controllers. You can see where I leverage a va_list in the where clause so I can inject that string right into my predicate. Here are some other methods you could implement:
- (NSArray *) findAll:(Class)entity
sortByKey:(NSString *)key
ascending:(BOOL)ascending;
- (NSArray *) findAll:(Class)entity
sortByKey:(NSString *)key
ascending:(BOOL)ascending
where:(NSString *)format, ...;
- (id) findFirst:(Class)entity
where:(NSString *)format, ...;
I'm not sure if this is the preferred way, but I've had a lot of success with this method. Hope this helps!
Check this link, this will help you a lot
Link: http://mobile.tutsplus.com/tutorials/iphone/iphone-sdk_store-data/
This cover 4 major ways to store data in iPhone with sample code.
1) NSUserDeafult
2) Property Lists
3) SQLLite
4) Core Data