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
This code, as it stands, will make the connection to the web service and retrieve the last 20 posts. However, there is one problem: you don’t see those posts anywhere. You need to implement delegate methods for
NSURLConnection
to collect the XML data returned from this request.
Figure 25.5 NSURLConnection flow chart
The delegate of an
NSURLConnection
is responsible for overseeing the connection and for collecting the data returned from the request. (This data is typically an XML or JSON document; for this web service, it is XML.) However, the data returned usually comes back in pieces, and it is the delegate’s job to collect the pieces and put them together.
In
ListViewController.m
, implement
connection:didReceiveData:
to put all of the data received by the connection into the instance variable
xmlData
.
When a connection has finished retrieving all of the data from a web service, it sends the message
connectionDidFinishLoading:
to its delegate. In this method, you are guaranteed to have the complete response from the web service request and can start working with that data. For now, implement
connectionDidFinishLoading:
in
ListViewController.m
to print out the string representation of that data to the console to make sure good stuff is coming back.
There is a possibility that a connection will fail. If an instance of
NSURLConnection
cannot make a connection to a web service, it sends its delegate the message
connection:didFailWithError:
. Note that this message gets sent for a
connection
failure, like having no Internet connectivity or if the server doesn’t exist. For other types of errors, such as data sent to a web service in the wrong format, the error information is returned in
connection:didReceiveData:
.
In
ListViewController.m
, implement
connection:didFailWithError:
to inform your application of a connection failure.
Try building and running your application. You should see the XML results in the console shortly after you launch the application. If you put your device in Airplane Mode (or if it is not connected to a network), you should see a friendly error message when you try to fetch again. (For now, you will have to restart the application from
Xcode
in order to refetch the data after you’ve received the error.)
The XML that comes back from the server looks something like this:
(If you aren’t seeing anything like this in your console, verify that you typed the URL correctly.)
Let’s break down the XML the server returned. The top-level element in this document is an
rss
element. It contains a
channel
element. That
channel
element has some metadata that describes it (a title and a description). Then, there is a series of
item
elements. Each
item
has a title, link, author, etc. and represents a single post on the forum.
In a moment, you will create two new classes,
RSSChannel
and
RSSItem
, to represent the
channel
and
item
elements. The
ListViewController
will have an instance variable for the
RSSChannel
. The
RSSChannel
will hold an array of
RSSItem
s. Each
RSSItem
will be displayed as a row in the table view. Both
RSSChannel
and
RSSItem
will retain some of their metadata as instance variables, as shown in
Figure 25.6
.
Figure 25.6 Model object graph
To parse the XML, you will use the class
NSXMLParser
. An
NSXMLParser
instance takes a chunk of XML data and reads it line by line. As it finds interesting information, it sends messages to its delegate, like,
“
I found a new element tag,
”
or
“
I found a string inside of an element.
”
The delegate object is responsible for interpreting what these messages mean in the context of the application.
In
ListViewController.m
, delete the code you wrote in
connectionDidFinishLoading:
to log the XML. Replace it with code to kick off the parsing and set the parser’s delegate to point at the instance of
ListViewController
.
The delegate of the parser,
ListViewController
, will receive a message when the parser finds a new element, another message when it finds a string within an element, and another when an element is closed.
For example, if a parser saw this XML:
it would send its delegate three consecutive messages:
“
I found a new element:
‘
title
’
,
”
then,
“
I found a string:
‘
Big Nerd Ranch
’
,
”
and finally,
“
I found the end of an element:
‘
title
’
.
”
These messages are found in the
NSXMLParserDelegate
protocol:
The
namespaceURI
,
qualifiedName
, and
attributes
arguments are for more complex XML, and we’ll return to them at the end of the chapter.
It is up to the
ListViewController
to make sense of that series of messages, and it does this by constructing an object tree that represents the XML feed. In this case, after the XML is parsed, there will be an instance of
RSSChannel
that contains a number of
RSSItem
instances. Here are the steps to constructing the tree:
This list doesn’t seem too daunting. However, there is one issue that makes it difficult: the parser doesn’t remember anything about what it has parsed. A parser may report,
“
I found a title element.
”
Its next report is
“
Now I’ve found the string inside an element.
”
At this point, if you asked the parser which element that string was inside, it couldn’t tell you. It only knows about the string it just found. This leaves the burden of tracking state on the parser’s delegate, and maintaining the state for an entire tree of objects in a single object is cumbersome.
Instead, you will spread out the logic for handling messages from the parser among the classes involved. If the last found element is a
channel
, then that instance of
RSSChannel
will be responsible for handling what the parser spits out next. The same goes for
RSSItem
; it will be responsible for grabbing its own
title
and
link
strings.
“
But the parser can only have one delegate,
”
you say. And you’re right; it can only have one delegate
at a time
. We can change the delegate of an
NSXMLParser
whenever we please, and the parser will keep chugging through the XML and sending messages to its current delegate. The flow of the parser and the related objects is shown in
Figure 25.7
.
Figure 25.7 Flow diagram of XML being parsed into a tree, creating the tree
When the parser finds the end of an element, it tells its delegate. If the delegate is the object that represents that element, that object returns control to the previous delegate (
Figure 25.8
).
Figure 25.8 Flow diagram of XML being parsed into a tree, back up the tree
Now that we have a plan, let’s get to work. Create a new
NSObject
subclass named
RSSChannel
. A channel object needs to hold some metadata, an array of
RSSItem
instances, and a pointer back to the previous parser delegate. In
RSSChannel.h
, add these properties:
In
RSSChannel.m
, synthesize the properties and override
init
.
Back in
ListViewController.h
, add an instance variable to hold an
RSSChannel
object and have the class conform to the
NSXMLParserDelegate
protocol.
In
ListViewController.m
, implement an
NSXMLParserDelegate
method to catch the start of a
channel
element. Also, at the top of the file, import the header for
RSSChannel
.
Build and run the application. You should see a log message that the channel was found. If you don’t see this message, double-check that the URL you typed in
fetchEntries
is correct.
Now that the channel is sometimes the parser’s delegate, it needs to implement
NSXMLParserDelegate
methods to handle the XML. The
RSSChannel
instance will catch the metadata it cares about along with any
item
elements.
The channel is interested in the
title
and
description
metadata elements, and you will store those strings that the parser finds in the appropriate instance variables. When the start of one of these elements is found, an
NSMutableString
instance will be created. When the parser finds a string, that string will be concatenated to the mutable string.
In
RSSChannel.h
, declare that the class conforms to
NSXMLParserDelegate
and add an instance variable for the mutable string.
In
RSSChannel.m
, implement one of the
NSXMLParserDelegate
methods to catch the metadata.
Note that
currentString
points at the same object as the appropriate instance variable – either
title
or
infoString
(
Figure 25.9
).
Figure 25.9 Two variables pointing at the same object
This means that when you append characters to the
currentString
, you are also appending them to the
title
or to the
infoString
.
In
RSSChannel.m
, implement the
parser:foundCharacters:
method.
When the parser finds the end of the
channel
element, the channel object will return control of the parser to the
ListViewController
. Implement this method in
RSSChannel.m
.