Consume web feed using a factory with abstract parser

Here we're going to go through an implementation of parsing a web feed that I recently used in a project. This comprises of:

  1. A xml message
  2. A service call layer
  3. A parser - in two parts
  4. A factory

XML Message

<?xml version="1.0" encoding="utf-8" ?>
<getUserDetailsResponse>
  <getUserDetails>
    <userModel>
      <city>London</city>
      <email>[email protected]</email>
      <faxNumber>1234567891</faxNumber>
      <firstName>Fred</firstName>
      <lastName>Jones</lastName>
      <phoneNumber>1234567891</phoneNumber>
      <postCode>IG1 1YT</postCode>
      <street1>street1</street1>
      <street2>street2</street2>
    </userModel>
  </getUserDetails>
</getUserDetailsResponse>

So above is the xml that we want to parse through to create our data objects with, it has one user - Fred. In order to get this xml we need to make a web service call

A service call layer

The service call layer is a singleton class. It's a singleton because in this project I would only ever be making one web service call at a time so it didn't make sense to keep make/destroying this class. It also enabled me to cancel the connection if the user moved off the tab before all the data had been download, as a requirement was that the user had to be presented with the most up-to-date data upon entering the tab. So all I had to do was call a method on singleton and not worry about if that instance was the instance I had used to create the connection in the first place.

WebserviceCompleteDelegate Protocol


@protocol webserviceCompleteDelegate

- (void) finishedReceivingData:(NSMutableData *)inData;
- (void) connectionFailed;

@end

We're use the above to start the parsing process for the xml that we receive or to handle a situation where the connection has entered an error state.

.h

@interface ServiceCall : NSObject{
    NSMutableData *xmlData;
    id <WebserviceCompleteDelegate> delegate;
    NSURLConnection *conn;
}

/*
 @method Connects to the webservice and requests user details
 @param UserID - string repesentation of unique userid
*/
-(void)setUserDetails:(NSString *)userID;

@end

.m

@implementation ServiceCall

@synthesize xmlData;
@synthesize delegate;
@synthesize conn;

#define GET_USER_DETAILS_SOAP_MESSAGE @"replace with your soap message"
#define GET_USER_DETAILS_WEBSERVICE_URL @"replace this with your web service URL"
#define GET_USER_DETAILS_SOAP_ACTION @"replace with name of your method name"

static ServiceCall *theServiceCall;

+ (ServiceCall *)theServiceCall{
    if (theServiceCall == nil) {
        theServiceCall = [[ServiceCall alloc] init];
    }

    return theServiceCall;
}

-(void)setUserDetails:(NSString *)userID{
    if ([self webserviceReachableTemplate]) {//checks for an active internet connection

        if (userFactory == nil) {
            userFactory = [[[UserFactory alloc] initForNotification] retain];//this object lives as long as the application is running
        }

                NSString *soapMessage = [NSString stringWithFormat:GET_USER_DETAILS_SOAP_MESSAGE, userID];

        NSMutableURLRequest *req = [self getHTTPRequestUsingSOAPMessage:soapMessage withSOAPAction:GET_USER_DETAILS_SOAP_ACTION withWebServiceUrl:GET_USER_DETAILS_WEBSERVICE_URL];     

        [soapMessage release];

        GetUserDetailsParser *parser = [[GetUserDetailsParser alloc] init];

        [self connectToWebServiceUsingRequest:req parser:parser];
        [parser release];

    }
}

A lots happening in the above code, first we call webserviceReachableTemplate which is a method that checks for an active internet connection, that we can connect to the server and that the service is reachable, using Apple's Reachability code. If either proves false in which case it fires a notification which the UI catches and acts upon informing the user as to the connection issue or it proves true and we move deeper into the method.

We then set up (if we need to) a UserFactory object, its this factory that will be responsible for creating a user object from the parsed XML that we send it. As we don't want the parser to do anything other than parse xml, we create the factory here to ensure that it is active for when the parsing is finished and the notification (trust me on this one it will make sense)

Next comes the SOAP message itself which you will need to configure to access your web service. The message is mostly defined in the #define statement above, all we do is add the userid variable.

Now we pass our SOAP message and SOAP action call to the another method getHTTPRequestUsingSOAPMessage, lets pop into that method just now:

-(NSMutableURLRequest *)getHTTPRequestUsingSOAPMessage: (NSMutableString *)aSOAPMessage withSOAPAction:(NSString *)aSOAPAction withWebServiceUrl:(NSString*) webServiceUrl{
    NSURL *url = [NSURL URLWithString: webServiceUrl];
    NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];

    NSString *msgLength = [NSString stringWithFormat:@"%d", [aSOAPMessage length]];

    [req addValue:@"text/xml; charset=utf-8"  forHTTPHeaderField:@"Content-Type"];
    [req addValue:aSOAPAction forHTTPHeaderField:@"SOAPAction"];
    [req addValue:msgLength forHTTPHeaderField:@"Content-Length"];
    [req setHTTPMethod:@"POST"];
    [req setHTTPBody: [aSOAPMessage dataUsingEncoding:NSUTF8StringEncoding]];

    return req;
}

In the above code we take the SOAP message, action and webservice url and and construct a HTTP request.

Back to the original code, we then create an the NSXMLParser object and again this is passed off to another helper method:

-(void)connectToWebServiceUsingRequest:(NSMutableURLRequest *)aRequest parser:(id)inParser{

    conn = [[NSURLConnection alloc] initWithRequest:aRequest delegate:self];

    if (!conn){
        NSNotification *note = [NSNotification notificationWithName:@"connectionFailure" object:nil];
        [[NSNotificationCenter defaultCenter] postNotification:note];
    }else {
        self.delegate = inParser; //instance of xml parser
    }
}

So in the above code we make our connection conn giving it the request which contains the SOAP message, action and url, alongside the necessary HTTP setup. We also give the self as delegate which will be run when we start to receive connection information. We then check to make sure that the connection was a success, if not we fire off a notification for the UI to catch. An important aside to note here is that conn is an instance variable this is so that i can drop the connection if I need to from outside the class i.e. if the user moves off the tab that is being updated, I drop the connection.

The else branch has self.delegate = inParser; the delegate is an instance variable which we shall use in the NSURLConnection methods.

#pragma mark -
#pragma mark NSURLConnection methods

-(void)connection:(NSURLConnection *) connection didReceiveResponse:(NSURLResponse *) response
{
    if (xmlData == nil) {
        xmlData = [[NSMutableData alloc] init];
    }

    [xmlData setLength: 0];
}

-(void)connection:(NSURLConnection *) connection didReceiveData:(NSData *) data {
    [xmlData appendData:data];
}

-(void)connection:(NSURLConnection *) connection didFailWithError:(NSError *) error {
    [delegate connectionFailed];
    [conn release];
    conn = nil;
    [xmlData release];
    xmlData = nil;
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [delegate finishedReceivingData:xmlData];
    [conn release];
    conn = nil;
    [xmlData release];
    xmlData = nil;

}

The above should be fairly straight forward, we basically just handle the possible states that the NSURLConnection object can get into. Of note is the [delegate finishedReceivingData:xmlData];, this passes the received data onto our xml parser instance.

A parser - in two parts

Part 1

The first part is my interpretation of what an abstract class in Objective-C. I wanted to create a top level parent that implemented the NSXMLParserDelegate protocol and some of the common methods but forced the child class to implement the more specific methods. If I just left these specific methods out then I got compile time warnings (which really annoys me) so I decided to add these methods but make them throw exceptions (with meaningful messages) if they were not overridden by a child implementation. I know I could have just left the protocol declaration out of the parent class and included it into the child class but this way seemed more fun ;-)

.h

/*
 @class An abstract xml parser class that handles receiving data and parsing the xml content.
 */
@interface AbstractParser : NSObject <NSXMLParserDelegate, WebserviceCompleteDelegate> {
    NSMutableString *foundCharacters;
    BOOL accumulator;
}

@end

In the above we delcare that this class is going to conform to the NSXMLParserDelegate protocol and the WebserviceCompleteDelegate. The WebserviceCompleteDelegate protocol is a custom protocol that, if you remember back, we made our delegate object in the servicecalllayer conform to. It has two methods:

  • connectionFailed
  • finishedReceivingData

.m


#import "AbstractParser.h"

@implementation AbstractParser

-(id)init{

    self = [super init];

    foundCharacters = [[NSMutableString alloc] init];

    return self;
}

#pragma mark -
#pragma mark NSXMLParserDelegate methods

- (void)parserDidStartDocument:(NSXMLParser *)parser{

}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{

    [NSException raise:NSInternalInconsistencyException
                format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    if (accumulator) {
        [foundCharacters appendString:string];
    }
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    [NSException raise:NSInternalInconsistencyException
                format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];

}

- (void)parserDidEndDocument:(NSXMLParser *)parser{

    [NSException raise:NSInternalInconsistencyException
                format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];
}

#pragma mark -
#pragma mark Webservice delegate methods

-(void)finishedReceivingData:(NSMutableData *)inData{
    NSString *theXML = [[NSString alloc] initWithBytes:[inData mutableBytes]
                                                length:[inData length]
                                              encoding:NSUTF8StringEncoding];

    NSData* data = [theXML dataUsingEncoding:NSUTF8StringEncoding];
    [theXML release];

    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];

    [parser setDelegate:self];
    [parser parse];

    [parser release];
}

-(void) connectionFailed{
    NSNotification *note = [NSNotification notificationWithName:@"connectionFailure" object:nil];
    [[NSNotificationCenter defaultCenter] postNotification:note];
}

#pragma -
#pragma Memory management

-(void)dealloc{

    [foundCharacters release];
    foundCharacters = nil;

    [super dealloc];
}

@end

In the above code we get to see the implementations of the two WebserviceCompleteDelegate methods. connectionFailed simply fires off a notification for the UI to catch. -(void)finishedReceivingData: takes the xml string, converts it into NSData and starts off the parsing process.

If parsing process is started the other methods will get called. These methods are common across all xml parser delegate classes. - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string is a method that gets called every time when receive new characters to be processed, in this implementation we are only interested in recording those character if a boolean flag accumulator is set to true. It will be the responsilbilty of the child to decide what xml elements characters are worthy of recording.

Part Two

The specific implementation of the abstract parser

.h

#import "AbstractParser.h" /* @class XML parser to consume the GetUserDetails web feed */ @interface GetUserDetailsParser : AbstractParser { NSMutableDictionary *parsedContent; } @end

So parsedContent is going to hold the elements contained in the xml feed.

.m

@implementation GetUserDetailsParser

-(id)init{
    self = [super init];

    accumulator = FALSE;

    return self;
}

#pragma mark -
#pragma mark NSXMLParserDelegate methods


- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{

    if ([elementName isEqual:@"getUserDetails"]) {

        if (parsedContent == nil) {
            parsedContent = [[NSMutableDictionary alloc] init];
        }

        accumulator = TRUE;
    }
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {

    if ([elementName isEqual:@"getUserDetails"]) {
        accumulator = FALSE;
    }

    if (accumulator) {
        if ([foundCharacters length] != 0) {
            [parsedContent setObject:[[foundCharacters copy] autorelease] forKey:elementName];
            [foundCharacters setString:@""];
        }
    }
}

- (void)parserDidEndDocument:(NSXMLParser *)parser{
    NSNotification *note = [NSNotification notificationWithName:@"finishedParsingUser" object:parsedContent];
    [[NSNotificationCenter defaultCenter] postNotification:note];
}

-(void)dealloc{

    [parsedContent release];
    parsedContent = nil;

    [super dealloc];
}

@end

In the above we need only pay attention to xml that occurs between the getUserDetails elements so we only set accumulator to TRUE when we find that opening element. At the end of each element (that occurs inside getUserDetails elements) we puts its value into a dictionary using the element name as the key. When we finish parsing this xml document we said out a notification that the UserFactory object will catch (init'd in the servicecall layer).

A factory

.h

/*
 @class Creates a user, sends out a notification when user is created
 */
@interface UserFactory : NSObject {

}

/*
 @method - Custom init method, that sets up the user factory and subscribes it to notifications
 */
-(id)initForNotification;

@end

In the above we declare a custom initialiser, that creates the object and signs it up for the notification.

.m


@interface UserFactory(Private)

/*
 @method - Listens for user parsed notifications and handles that notification setting off the user creatation process
 @param - note, notification containing dictionary with parsed data
 */
-(void)handleUserNotification:(NSNotification *)note;

/*
 @method - Creates a user object
 @param - Dictionary containing data to be used to create a user object
 @return - User object
 */
-(User *)createUser:(NSDictionary *)data;

/*
 @method - Send notification when user object is created
 @param - User object
 */
-(void)sendNotificationThatUserIsCreated:(User *)user;

@end

@implementation UserFactory

-(id)initForNotification{
    self = [super init];

    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self selector:@selector(handleUserNotification:) name:@"finishedParsingUser" object:nil];

    return self;
}

#pragma mark -
#pragma mark Notification methods

-(void)handleUserNotification:(NSNotification *)note{

    User *user = [self createUser:[note object]];
    [self sendNotificationThatUserIsCreated:user];
}

-(void)sendNotificationThatUserIsCreated:(User *)user{
    NSNotification *note = [NSNotification notificationWithName:@"userCreated" object:user];
    [[NSNotificationCenter defaultCenter] postNotification:note];
}

#pragma mark -
#pragma mark Actioning methods

-(User *)createUser:(NSDictionary *)data{

    User *user = [[[User alloc] init] autorelease];

    for (id key in data){

        NSString *capitalizeElementName = [key stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[key substringToIndex:1] uppercaseString]];
        NSString *selector = [NSString stringWithFormat:@"set%@:", capitalizeElementName];
        SEL elementSelector = NSSelectorFromString(selector);

            if ([user respondsToSelector:elementSelector]) {
            [user performSelector:elementSelector withObject:[data objectForKey:key]];
        }
    }

    return user;

}

#pragma mark -
#pragma mark Memory management

-(void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [super dealloc];
}

@end

The first thing we do is use extensions @interface UserFactory(Private) to add our private methods. In the -(User *)createUser:, I've compromised by Object-Orientated briefs by indirectly coupling my User object to the element names used in the XML but as the actual XML and User object contains many more elements than have been shown here it made sense rather than using a lot(!) of if statements. This method is called when the factory object receives the notification: finishedParsingUser. It works its way through the dictionary and creates selectors based on the dictionary keys. As I have set up my User object to have these keys as properties, it automatically creates a getter and setter for each, with the setter taking the signature: -(void)setProperty:(type)obj. I recreate this signature, make sure that the User object has it as a method and call it passing the dictionary value as its value. When Fred (the User object) is fully assembled I then pass him out and someone else can deal with him, again using a notification.

And that's us. Well done you making it to the end of that, if you have any questions please do post them and i'll try to answer them. Phew!!

We have sneakedly used two other design patterns, Observer (notifications) and Strategy (delegate).