Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online
Authors: Aaron Hillegass,Joe Conway
Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming
Most of the applications you’ve built in this book have been based on the Model-View-Controller design pattern. The exception is
Homepwner
, which is based on the Model-View-Controller-Store design pattern. In this chapter, we’re going to look more closely at store objects, MVCS, and when MVCS is the better approach for designing an application.
This is a fairly advanced chapter. Until now, you’ve been leveraging the power of existing classes that are built into the iOS SDK. Here, you will write a number of classes of your own that do real work. It will be difficult, but rewarding.
Many applications (and especially mobile applications) make use of external sources of data. External sources of data include web servers, databases, the filesystem, location hardware, the camera, etc. Basically, it’s anything that is not already in memory.
If your application interfaces with an external source of data, then Model-View-Controller-Store is a better approach than Model-View-Controller.
To explain why, first, let’s define a term:
request logic
is code that fetches things from an external source. Examples of request logic are creating an
NSFetchRequest
in Core Data, preparing an
NSURLRequest
for
NSURLConnection
, and loading a file with
NSKeyedUnarchiver
. Request logic is typically long, complicated, and full of source-specific details.
In MVC, request logic is the responsibility of controller objects (
Figure 28.1
). However, this is not a good solution. Including request logic in a controller muddies up the class and makes it harder to see how the controller is managing the flow of the application and coordinating the application’s model and view objects.
Figure 28.1 Standard MVC
In addition, an application may interact with multiple sources of data, and a controller would have to know the intimate details of communicating with each (
Figure 28.2
). For example, a single controller might have to interface with the location hardware, a web server, and another iOS device all at the same time. That controller would have a painfully dense implementation file.
Figure 28.2 Messy MVC
An application can have multiple controllers interfacing with an external source, too. Putting the request logic in each controller leads to redundant code. (On the other hand, if you kept the request logic in one controller, you’d end up with other controllers having unnatural dependencies on the one holding the request logic.)
Figure 28.3 Messy MVC
The possibility of multiple controllers interfacing with an external source leads to another complication. Often, you need keep track of the application’s exchanges with an external source. For example, a web service might require you to pass authentication credentials each time you make a service call. If multiple controllers need to make calls to this web service, then each one has to know about these credentials and know how to send them to the server (
Figure 28.3
). Caching data is another example of a problem here. If you are caching responses from a web server, each controller needs to make decisions on when to cache, when to load from the cache, and where the cache is stored.
Model-View-Controller-Store puts request logic into a separate object, and we call this object a
store
(
Figure 28.4
). Using a store object minimizes redundant code and simplifies the code that fetches and saves data. Most importantly, it moves the logic for dealing with an external source into a tidy class with a clear and focused goal. This makes code easier to understand, which makes it easier to maintain and debug, as well as share with other programmers on your team.
Figure 28.4 Model-View-Controller-Store diagram
You’ve already seen and used MVCS in the Homepwner application. In
Homepwner
, you had two store objects:
BNRItemStore
and
BNRImageStore
. This freed the table view controller to focus on its work as the table view’s data source instead of mucking about with fetching individual
BNRItem
s.
But what about the application you just completed,
Nerdfeed
?
Nerdfeed
has request logic for getting data from a web service, and that code is in a controller object –
ListViewController
.
Let’s fix that now. In this chapter, we will add a store class to
Nerdfeed
and move all of the request logic out of
ListViewController
. We will also add another web service request and new features to
Nerdfeed
to demonstrate the benefits of MVCS.
Right now, the request logic in
Nerdfeed
is in a controller. Open
Nerdfeed.xcodeproj
and locate
ListViewController.m
.
ListViewController.m
is a big file already, and adding new features to this class will only get more difficult as it becomes larger. However, if you look at the code in this class, you can break it into two categories. First, there is the code that deals with populating views with model objects and handles user interaction. These are the data source and delegate methods for
UITableView
, the action methods, and the methods that control the flow of the application, like
fetchEntries
.
The other code in this file is request logic – preparing an
NSURLConnection
, receiving its response, and handling the parsing of the data that returns from the server. In MVCS, a controller should not be responsible for this code, so we’re going to create a store object to take over these responsibilities.
Create a new
NSObject
subclass and name it
BNRFeedStore
. This object will be the point of contact between your controllers and the server that hosts the BNR forum and RSS feed.
A store object is typically a singleton. Because
BNRFeedStore
is a singleton, all controllers access the same instance of
BNRFeedStore
, which allows us to easily cache and store information about the server in one place. In
BNRFeedStore.h
, declare a new method that returns the single instance of
BNRFeedStore
.
In
BNRFeedStore.m
, implement this method.
When
Nerdfeed
launches, it fetches the RSS feed. The container object for the RSS feed is an instance of
RSSChannel
, and each individual post is represented by an
RSSItem
within that channel. Remembering that a store’s job is to unburden the controller, the store should make it really easy for a controller to fetch the
RSSChannel
instance.
Think back to another store you wrote – the
BNRItemStore
from
Homepwner
. In that application, the controller simply asked the store for all of the items, and the store loaded the right file from the filesystem and returned the instances of
BNRItem
the controller asked for. It looked like this:
BNRFeedStore
should make it just as easy for
ListViewController
. However, there is a problem: unlike loading from the filesystem, it takes time to make the request to a web server.
BNRFeedStore
can’t just return the
RSSChannel
from a method because that would block the main thread while the request was being processed. This would prevent the UI from redrawing or accepting events and generally annoy the user:
Instead, the request needs to be handled asynchronously. The
ListViewController
will make the request and supply a callback for when the request finishes. The callback will have access to the
RSSChannel
so the
ListViewController
can grab the channel and reload its table view.
You have already done something like this. In
Whereami
, the controller made a request to an instance of
CLLocationManager
to find the current location. When the request completed, the location manager returned an instance of
CLLocation
to the controller via delegation. Believe it or not, a
CLLocationManager
is a store object, and it returned data asynchronously to the requesting controller (
Figure 28.5
).
Figure 28.5 Flow of Whereami
So we could use delegation to return the
RSSChannel
from the store, but here we run into another problem.
BNRFeedStore
is a singleton, so delegation won’t work. Multiple controllers may want to make requests to the
BNRFeedStore
at the same time, but only one can be the delegate.
This is where blocks come in handy.
ListViewController
can make a request and supply a block to be called when the request is complete. The store will execute this block with the newly fetched
RSSChannel
as an argument. The block’s code will set the
channel
of the
ListViewController
and reload the
UITableView
. In
BNRFeedStore.h
, declare a new method and forward declare
RSSChannel
.
When a controller wants the RSS feed, it will send the message
fetchRSSFeedWithCompletion:
to
BNRFeedStore
. This is a very simple request for the controller, but a fairly complicated one for the
BNRFeedStore
. The controller doesn’t care, though,
how
the RSS feed is fetched, only that it is fetched. The complications are the responsibility of the store object.
Take a look at the form of the block that will be called when the request finishes. It takes two arguments: a pointer to an
RSSChannel
and a pointer to an
NSError
object. If the request was a success, the
RSSChannel
will be passed as an argument, and the
NSError
will be
nil
. If there was a problem, an instance of
NSError
will be passed as an argument, and the
RSSChannel
will be
nil
.
Let’s hold off on the implementation of
fetchRSSFeedWithCompletion:
for now and just implement it with an empty body in
BNRFeedStore.m
.