views:

149

answers:

3

Hello,

I have created a RSS parser and 3 TableViews and it parses the RSS files fine but I don't know how to notify the TableViewController when parsing has ended so it can update the view. The TableViewController initiates the parser and the parsing of a feed.

parser = [[RSSParser alloc] initWithURL:@"http://randomfeed.com"];

I can access the single feed items like

[parser feedItems];

In parser.m i have implemented the delegate methods of NSXMLParser:

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
- (void)parserDidEndDocument:(NSXMLParser *)parser 

So how do i get parserDidEndDocument to notify my controllers so i can add the data to the tableview.

Cheers from a obj-c beginner.

+1  A: 

Hi If you need multiple object to be notified of a single event the delegate approach usually starts to get messy.

NSNotifications are a good alternative.

    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(reachabilityChanged:) name: kInternetAvailableNotification object: nil];

The above adds a "listener" to the current object that listens for anyone firing the kInternetAvailableNotification notification. When the event happens the method "reachabilityChanged" is called.

In your dealloc method remember to unregister for the notification:

- (void)dealloc {

    [[NSNotificationCenter defaultCenter] 
     removeObserver:self 
     name:kInternetAvailableNotification 
     object:nil];   

    [super dealloc];
}

Anywhere in your code you can now notify listeners about a certain event:

[[NSNotificationCenter defaultCenter]
 postNotificationName:kInternetAvailableNotification
 object:nil];

NSNotifications can send along dictionaries with object that the listeners can use.

So every Controller that needs to know when new data is available can register for the kInternetAvailableNotification notification (it is just a name defined by an NSString) and be informed.

This is a short run-through:)

RickiG
Sorry my fault, the Controller should only be notified about its own instance of the RSSParser.
objneodude
Add-on: I want to somehow run [mytableview reloadData] when parserDidEndDocument is called.
objneodude
Ahh ok, then you want the delegate pattern:) I'll add it to my answer.
RickiG
Thanks mate, i appreciate your help.
objneodude
A: 

Ok the delegation pattern is widely used in the Cocoa framework.

The file that parses data must have a protocol, anyone wanting callbacks form this Class must implement the methods from the protocol:

    //XMLParser.h

    //import statements here ..

    @protocol XMLParserDelegate

    - (void) parserDidFinish:(NSArray*) theParsedData; //have as many methods as you please, didFail, doingProgress etc.

    @end

    @interface XMLParser : NSObject {

    id <XMLParserDelegate> delegate; //we don't know what type of object this will be, only that it will adhere to the XMLParserDelegate protocol.

    }

@property(assign) id delegate;
    //methods

    @end

In the implementation of the XMLParser:

@implementation XMLParser

@synthesize delegate;

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

[self.delegate parserDidFinish:dataYouCollectedArray]; //this is where it happens.

}

So in your controllers interface file you says that you will adhere to the XMLParserDelegate protocol.

//MyController.h

#import "XMLParser.h"

@interface MyController : UIViewController <XMLParserDelegate> { //this is where you "promise" to implement the methods of the protocol.

}

In the MyController.m file you now instantiate the XMLParser.

@implementation MyController

- (void) init {
XMLParser *parser = [[XMLParser alloc] init];
[parser setDelegate:self] //now the parser has a reference to this object.
[parser start];

}

- (void) parserDidFinish:(NSArray*) results {

//now you have your results in the controller, can set it as the data source of the tableView and call tableView.reloadData;
}

This is a great pattern that loosely couples the caller and responder without having them know anything other that what the protocol dictates, about each other.

If I have a view element that is confined to its own functionality, like, say, a clock. I would have a ClockViewController and it would instantiate arm, dials, etc. They would all be linked to the clock and alert the clock controller about their actions using this pattern. This way I can use the clock arm or dial in other code as I please, as long as the object instantiating them adheres to the ClockArmDelegate protocol.

Hope it makes sense:) it is the, Im pretty sure, most used pattern in Cocoa.

RickiG
Thanks for that example, made me understand how to use it fully.
objneodude
A: 

I see three possible solutions here:

  • NSNotifications. Easy to implement, but not always great for maintainability (you never know which object is registered to your notifications)
  • An array of delegates (instead of a single delegate), and invoke the delegate methods on every object of the array. Three20 uses this pattern a lot (and Three20 is a good reference), but it still looks like the delegate pattern is being stretched too far, IMHO.
  • Key-Value Observing on your data object. For instance, if you have a property "RSSItems" on your model that contains the parsed RSS data, you would register each of your controllers to listen for changes to the RSSItems object :

    [rssParser addObserver:yourViewController
                forKeyPath:@"RSSItems"
                   options:NSKeyValueObservingOptionNew
                   context:NULL];
    

Then, in your parser:didEndElement: method, you would set the content of RSSItem, thus triggering the KVO notification.

I think this third option would be the best: it's a standard Cocoa mechanism, it doesn't require system-wide notifications, and it doesn't stretch the delegate pattern too far.

Kemenaran