Reading an RSS Feed

I previously used the example of an RSS feed reader app to illustrate some table view techniques so I thought it was about time that I looked at actually reading and parsing an RSS feed for real. The problem can be divided into two parts, the first that I cover here is actually retrieving the feed contents over the network. The second part is then parsing the XML feed contents to extract the post information that we actually want to read. I will cover the XML parsing part in a following post.

ASIHTTPRequest

You can use the standard core foundation network or NSURLConnection methods to retrieve the contents of a remote URL. I prefer to use the excellent ASIHTTPRequest wrapper developed by Ben Copsey which does a good job of doing all the hard work for me. To get started you need to download the Objective-C source files and add them to the Xcode project. You also need to add Apple’s Reachability class or the replacement by Andrew Donoho.

As well as adding the source files you also need to add some additional frameworks to the project. The frameworks to add are as follows:

  • CFNetwork.framework
  • SystemConfiguration.framework
  • MobileCoreServices.framework
  • CoreGraphics.framework
  • libz.1.2.3.dylib

If you are not sure how to add the frameworks, right click on the project target in the Xcode Groups & Files window and select Get Info. Then in the General tab use the + button at the bottom of the Linked Libraries window. When you have finished it should look something like this:

I should also note that building against the iOS 4.1 SDK there is a compiler warning generated for version 1.7 of ASIHTTPRequest. The warning is for ASIDownloadCache.m:

'createDirectoryAtPath:attributes:' is deprecated
 (declared at /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/
 iPhoneSimulator4.1.sdk/System/Library/Frameworks/Foundation.framework/
 Headers/NSFileManager.h:168)

For the purposes of this example it does not really matter but if you want to fix the warning you can replace the offending line:

[[NSFileManager defaultManager] createDirectoryAtPath:directory
                                           attributes:nil];

with the following:

[[NSFileManager defaultManager] createDirectoryAtPath:directory 
                          withIntermediateDirectories:NO
                                           attributes:NO
                                                error:nil];

Retrieving the Feed

I already have a model class named Feed that I intended to hide the details of retrieving posts. The first step is using ASIHTTPRequest to fetch the RSS feed. To do that I will first add some useful ivars to the Feed class:

@class ASIHTTPRequest;   
@interface Feed : NSObject {
    NSURL *feedURL;
    ASIHTTPRequest *feedRequest;
}

@property (nonatomic, copy) NSURL *feedURL;
@property (nonatomic, retain) ASIHTTPRequest *feedRequest;

The feedURL is just used to store the URL of the RSS feed that we will retrieve. The feedRequest ivar will be used to store an ASIHTTPRequest object which is the wrapper object that hides the details of the underlying network libraries.

To initialize a feed object we need a new initializer that takes as an argument the URL we want to retrieve. This method does not do much right now other than store the URL:

-(id)initWithURL:(NSURL *)sourceURL {	
    if (self = [super init]) {	
        self.feedURL = sourceURL;
    }
    return self;
}

To actually initiate some network communication I have added an instance method named refresh:

- (void)refresh {
    self.feedRequest = [ASIHTTPRequest requestWithURL:feedURL];
    [feedRequest setDelegate:self];
    [feedRequest startAsynchronous];
}

This method creates an ASIHTTPRequest object with the URL of the feed, sets our feed object to be the delegate and then starts an asynchronous request so that we do not block the user interface whilst the feed is being retrieved. All we need to do now is implement two delegate methods to handle a success and failure. First the method to handle a successful request:

- (void)requestFinished:(ASIHTTPRequest *)request {
    NSData *responseData = [request responseData];
  
    // Parse the response data
    // ...
  
    [[NSNotificationCenter defaultCenter]
      postNotificationName:kFeederReloadCompletedNotification
      object:nil];	
}

Since I am not yet doing anything useful with the data this is trivial right now. I know that I need a mechanism to tell the controller class when the download is complete. I could use delegation to do this but I think in this case it is cleaner to send a notification when the download finishes. My controlling class can than register for the notification and will be called when it has some new data to display.

The method to handle a failed request is also simple (again I do not do anything useful with the error message right now other than log it):

- (void)requestFailed:(ASIHTTPRequest *)request {
    NSError *error = [request error];
    NSLog(@"requestFailed: %@", [error localizedDescription]);
  
    [[NSNotificationCenter defaultCenter]
      postNotificationName:kFeederReloadFailedNotification
      object:nil];
}

Initialising the Feed

Now we have some groundwork prepared we can modify the FeedViewController to make use of the new feed class. The viewDidLoad method of the controller now looks like this:

- (void)viewDidLoad {
    [super viewDidLoad];
  
    static NSString *feedURLString = @"/blog/rss.xml";
    NSURL *feedURL = [NSURL URLWithString:feedURLString];
  
    self.feed = [[Feed alloc] initWithURL:feedURL];
    self.posts = [[NSMutableArray alloc] init];
  
    [[NSNotificationCenter defaultCenter] addObserver:self
      selector:@selector(feedChanged:)
      name:kFeederReloadCompletedNotification 
      object:nil];
    [feed refresh];
}

So when the view controller is first loaded an NSURL object is created with the URL of the RSS feed for this blog. We then initialise a new feed object with the NSURL and add ourselves as an observer to the notification we created previously to signal a successful feed download.

Finally we call the feed refresh method to actually trigger the network communication. The only thing left to do is to implement the method that is called when the notification arrives:

- (void)feedChanged:(NSNotification *)notification {
    NSArray *newPosts = [feed newPosts];
  
    if (newPosts) {
    
        [self.posts addObjectsFromArray:newPosts];
        [self.tableView reloadData];
        [self updateViewTitle];
        [newPosts release];
    }
}

Since we do not yet actually parse the downloaded feed there is no real data to display so for now I continue to call the newPosts method to generate some dummy data to display. Once we implement the feed parser we can change this to update the table with actual post data.

Next Steps

That is pretty much all that is needed to retrieve the feed data. The simplicity of the code owes a lot to the work that ASIHTTPRequest code does under the covers. It takes away the hard work of dealing with background communications and allows us to focus on the useful stuff. In the next post I hope to cover how to use the NSXMLParser to extract the post data from the RSS feed.