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
Before you implement the details of the store, you are going to implement the code in the controller that uses
fetchRSSFeedWithCompletion:
.
ListViewController
only needs to initiate the fetch and describe what will happen in the completion block it passes as the argument.
Right now, the
ListViewController
is responsible for creating the
NSURLRequest
and the
NSURLConnection
. It also implements the delegate methods for
NSURLConnection
that accumulate the response data from the server and execute code when the request either succeeds or fails. This code needs to be moved into the store; it is request logic. You can begin by deleting the body of
fetchEntries
in
ListViewController.m
.
Now let’s re-implement
fetchEntries
to leverage the store’s power. At the top of
ListViewController.m
, import the header for
BNRFeedStore
.
Enter the new code for
fetchEntries
in
ListViewController.m
.
Now, we won’t be able to build and run for some time, but let’s see what will happen (
Figure 28.6
).
ListViewController
asks
BNRFeedStore
for the RSS feed and says,
“
When you have the feed, do the stuff in this block.
”
The
ListViewController
moves on with its life, and the store gets to work.
Figure 28.6 Flow of Nerdfeed with BNRFeedStore
Look at the code in the block that gets executed if there is no
err
; it looks a lot like the code in
ListViewController
’s
connectionDidFinishLoading:
method. The code in the block that gets executed if there is an error looks just like the code in
connection:didFailWithError:
.
This, of course, is intentional. We are not changing what
Nerdfeed
does – only how it does it.
ListViewController
will no longer manage the details of the
NSURLConnection
. In MVCS, controllers don’t do that kind of work, store objects do.
ListViewController
now flows exceptionally well: it says,
“
Fetch these entries, and then do this if this happens or that if that happens.
”
All of this code is in one place instead of strewn over a number of methods. The code is easy to read, easy to maintain, and easy to debug.
With this code in place, you can clean up
ListViewController
. In
ListViewController.m
, you can delete the following methods. (The bodies of the methods have been omitted to save trees.)
In
ListViewController.h
, you can delete the instance variables
connection
and
xmlData
, as well as remove the protocol declaration for
NSXMLParserDelegate
.
Take a moment to scroll through
ListViewController.h
and
ListViewController.m
. These files are now very sleek and easy to follow.
ListViewController
has a primary focus now – presenting a table of
RSSItem
s. This is the purpose of store objects: let the controller focus on its job rather than the details of request logic.
Another thing to mention – in the declaration of
fetchRSSFeedWithCompletion:
in
BNRFeedStore.h
, you supplied the names of the arguments for the block argument. In
Chapter 27
, we mentioned that this wasn’t necessary because the names of the arguments only matter in the block literal.
However, when you were typing
fetchRSSFeedWithCompletion:
in
ListViewController.m
, you probably noticed that
Xcode
auto-completed the block literal for you and added in the names of the arguments for the block. This was pretty handy. So when declaring a block variable as a property or as an argument to a method, it’s good idea to specify the names of the arguments in the block to take advantage of auto-completion.
You can build the application to check for syntax errors, but running it won’t do you much good since
fetchRSSFeedWithCompletion:
hasn’t been implemented. In the next section, you will complete the store object and get
Nerdfeed
back up and running.
The code that was removed from
ListViewController
must be replaced in
BNRFeedStore
. The
BNRFeedStore
must handle preparing the
NSURLRequest
, kicking off the
NSURLConnection
, handling the response from the server, and starting the parser. The
ListViewController
simply asks for these actions to be initiated and supplies a callback for when they are completed.
A store’s job is to take a really simple request from a controller, like
“
fetch the RSS feed,
”
and prepare a detailed request tailored for the external source it interacts with. In this case, the detailed request is the web service call to
http://forums.bignerdranch.com
. In
BNRFeedStore.m
, begin implementing
fetchRSSFeedWithCompletion:
by preparing this request.
It is important that the
NSURLRequest
is created inside the store and not in
ListViewController
. It is not
ListViewController
’s job to understand how to interact with the web server; thus, it shouldn’t know about the URL or the arguments passed in that URL. In fact, the
ListViewController
shouldn’t even know that the Internet exists or what an
NSURL
is. It just wants an
RSSChannel
, and by golly, it’ll get one.
The store’s next step is to create an
NSURLConnection
to process this
NSURLRequest
. Your first intuition would be to create the
NSURLConnection
here in
fetchRSSFeedWithCompletion:
and set the delegate of the connection as the
BNRFeedStore
. However, what happens when we add more requests to the store?
For example, the store could pull the RSS feed from another server. The store, then, would have to be the delegate for a lot of
NSURLConnection
s. The
NSURLConnection
delegate methods that handle the response would become giant if-else statements to determine what data belongs to which request.
Instead of having the
BNRFeedStore
be the delegate for every
NSURLConnection
, we are going to create another class named
BNRConnection
. The sole purpose of an instance of this class will be to manage a single
NSURLConnection
.
BNRConnection
instances will take the request prepared by the
BNRFeedStore
, start their own
NSURLConnection
, and then receive all of the delegate messages from that
NSURLConnection
. This object will do its work without bothering the controller or the store until it has finished. When it finishes, it will execute the completion block supplied by
ListViewController
(
Figure 28.7
).
Figure 28.7 BNRConnection and NSURLConnection
This is an example of the
actor design pattern
. An actor object is used when you have a long running task and some code that needs to be executed after it completes. This kind of object is given the information it needs to perform the task and callbacks to execute when that task is done. The actor runs on its own thread without any further input and is destroyed when it is finished (
Figure 28.8
).
Figure 28.8 Actor design pattern
Create
BNRConnection
, the actor, as a new
NSObject
subclass.
BNRConnection
needs instance variables and properties to hold the connection, request, and callback. Modify
BNRConnection.h
to look like this:
For every request the
BNRFeedStore
makes, it will create an instance of
BNRConnection
. An instance of this class is a one-shot deal: it runs, it calls back, and then it is destroyed. The next time a request is made, another instance of
BNRConnection
will be created. The store’s responsibility here is to pass on the appropriate data (the request and callback block) to the
BNRConnection
so that it can function. In other words, a store object becomes a middle man: it takes the simple demands of a controller and sends its
BNRConnection
minions off to carry out the work.
In addition to the request and callback, the store will provide an
xmlRootObject
to the
BNRConnection
. This object will be the object that eventually gets returned to the controller. In this case, the
xmlRootObject
will be an instance of
RSSChannel
. The store will create an empty
RSSChannel
and give it to the
BNRConnection
. When the connection finishes, the
BNRConnection
will parse the data from the
NSURLConnection
into the
xmlRootObject
before handing it to the controller.
Before we write the code for
BNRConnection
, we will finish
fetchRSSFeedWithCompletion:
in
BNRFeedStore.m
. First, import the appropriate files into
BNRFeedStore.m
.
In
BNRFeedStore.m
, add code to
fetchRSSFeedWithCompletion:
that creates an instance of
BNRConnection
and sets it up with the request, completion block and empty root object.
We are now at the heart of the code that will transform
Nerdfeed
from a pedagogical example to a functional powerhouse: the code for
BNRConnection
. We’ve changed the code in
Nerdfeed
by working from the outside and moving in, allowing us to see the simplicity earned by using MVCS and then the work required for that simplicity. We are almost there.
An instance of
BNRConnection
, when started, will create an instance of
NSURLConnection
, initialize it with an
NSURLRequest
, and set itself as the delegate of that connection. In
BNRConnection.m
, implement the following two methods and synthesize the three properties.
Build the application and check for syntax errors.
There is one little hitch here. When the store creates an instance of
BNRConnection
and tells it to start, the store will no longer keep a reference to it. This technique is known as
“
fire and forget.
”
However, with ARC, we know that
“
forgetting
”
about an object will destroy it. Thus, the
BNRConnection
needs to be owned by something. At the top of
BNRConnection.m
, add a static
NSMutableArray
variable to hold a strong reference to all active
BNRConnection
s.
(As a bonus, you can use this connection list as a way to reference connections in progress. This is especially useful for cancelling a request.)
In
BNRConnection.m
, update
start
to add the connection to this array so that it doesn’t get destroyed when the store forgets about it.