2
votes

I am unable to populate the previewData NSMutableArray with this code. XML request is successfully retrieved, parsed but I am unable to populate the previewData array for tableviewController.

PreviewsController.h code:

#import <UIKit/UIKit.h>

@interface PreviewsController : UIViewController 
    <UITableViewDelegate, UITableViewDataSource>
{
    UITableView *previewList;
    NSString *enteredUsername;
    NSString *enteredPassword;
    NSMutableString *current_xml_element;
    NSMutableString *current_xml_value;
    NSMutableDictionary *item;
    NSMutableArray *previewData;
}

@property (nonatomic, retain) IBOutlet UITableView *previewList;
@property (nonatomic, retain) NSString *enteredUsername;
@property (nonatomic, retain) NSString *enteredPassword;
@property (nonatomic, retain) NSMutableString *current_xml_element;
@property (nonatomic, retain) NSMutableString *current_xml_value;
@property (nonatomic, retain) NSMutableArray *previewData;
@property (nonatomic, retain) NSMutableDictionary *item;

@end

PreviewsController.m code:

#import "PreviewsController.h"

@implementation PreviewsController

@synthesize previewList;
@synthesize enteredUsername, enteredPassword;
@synthesize current_xml_element, previewData, item, current_xml_value;

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.previewData = nil;

    [super viewDidUnload];
}

- (void)dealloc {
    [item release];
    [previewData release];
    [enteredUsername release];
    [enteredPassword release];
    [current_xml_element release];
    [super dealloc];
}

- (void)viewDidLoad {
    previewData = [[NSMutableArray alloc] init];

    // Fetch the XML output from API - Start
    NSString *APIURL = [[NSString alloc] initWithFormat:@"http://mysite.com/api/%@/%@", enteredUsername, enteredPassword];
    NSURL *url = [[NSURL alloc] initWithString:APIURL];
    NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];
    [APIURL release];
    [url release];

    [xmlParser setDelegate:self];
    [xmlParser parse];
    // Fetch the XML output from API - End

    [super viewDidLoad];
}

#pragma mark -
#pragma mark XML Request Methods

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

-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
    current_xml_element = [elementName copy];

    if ([elementName isEqualToString:@"subject"]) {
        item = [[[NSMutableDictionary alloc] init] autorelease];
        current_xml_value = [[NSMutableString alloc] init];
    }
}

-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    if ([current_xml_element isEqualToString:@"subject"]) {
        [current_xml_value appendString:string];
    }
}

-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    if ([elementName isEqualToString:@"subject"]) {
        [item setObject:current_xml_value forKey:@"subject"];
        [previewData addObject:[item copy]];
//      NSLog(@"array count: %@", [previewData count]);
    }
}

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

-(void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
    NSLog(@"Error occurred: %d - %@", [parseError code], [parseError localizedDescription]);
}

#pragma mark -
#pragma mark Table View Data Source Methods

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 
    return [previewData count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *SimpleTableIdentifier = @"SimpleTableIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SimpleTableIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SimpleTableIdentifier] autorelease];

    }
    NSUInteger row = [indexPath row];
    cell.textLabel.text = [previewData objectAtIndex:row];
    return cell;
}


@end

When I build and run this code, the following error is returned in console:

2009-10-15 23:13:55.296 PreviewMyEmail[29964:207] *** -[NSCFDictionary isEqualToString:]: unrecognized selector sent to instance 0x3839a60
2009-10-15 23:13:55.298 PreviewMyEmail[29964:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSCFDictionary isEqualToString:]: unrecognized selector sent to instance 0x3839a60'
2009-10-15 23:13:55.298 PreviewMyEmail[29964:207] Stack: (
    29307995,
    2531364681,
    29689915,
    29259382,
    29112002,
    3812419,
    13553,
    3063392,
    3029655,
    3106211,
    3070291,
    55820976,
    55820399,
    55818438,
    55817530,
    55851064,
    29094482,
    29091423,
    29088840,
    37398413,
    37398610,
    2781187,
    8436,
    8290
)

Where am I doing wrong? Thanks for all your help.

Cheers.

3
You have a memory leak. [item copy] returns a retained object, which you lose the reference to by immediately adding it to previewDataDave DeLong
Yep, it's a memory leak. If you do [[item copy] autorelease] you don't retain that extra reference.Alex

3 Answers

7
votes

Is previewData being initialized somewhere else? It looks to me like it does not have a value, given the code and output. [previewData count] would return nil, which is 0. Add previewData = [[NSMutableArray alloc] init]; at the beginning and it should work.

5
votes

The error might be generated in -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath, in particular the line:

cell.textLabel.text = [previewData objectAtIndex:row];

Here you're assigning an NSMutableDictionary to a property with type NSString. A development version of your app should show you the method name and line number in the stack trace.

Notes not related to error

Efficiency

Since the NS collection classes retain objects, there's no need to store a copy of item rather than the original, unless you want an immutable object.

Memory leaks

A few of the copied objects will produce memory leaks if there is more than one "subject" element. You can either handle the release yourself or use properties. Properties are easier to deal with, and thus less error prone. Why bother to define properties if you're not going to use them? Some of the properties aren't part of the public interface, and the class doesn't look to be for a library. If you want handle releasing yourself, remember to balance alloc/copy/retain in one method with auto-/release in a corresponding method. For parser:didStartElement:..., the corresponding method is parser:didEndElement:... I'm still trying to formulate a succinct way of describing this balance (coming soon to an SO question near you).

current_xml_element gets clobbered every time the parser descends into a child. Instead, you can set/unset a flag when you start/end processing a "subject" node. This will break if a "subject" node ever contains a child "subject" node, but such an XML document would be pathological and cause other problems (e.g. clobbering item and current_xml_value, thus causing memory leaks). An alternative to a flag is to store a stack of elements, pushing during didStartElement and popping during didEndElement.

PreviewsController.h:

@interface PreviewsController : UIViewController 
    <UITableViewDelegate, UITableViewDataSource>
{
    ...
    /* NSMutableString *current_xml_element; */
    BOOL current_element_is_subject;
    ...
}

...
/* @property (nonatomic, retain) NSMutableString *current_xml_element; */
@property (nonatomic, assign) BOOL current_element_is_subject;
...

@end

PreviewsController.m

-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {    
    if ([elementName isEqualToString:@"subject"]) {
        self.item = [NSMutableDictionary dictionaryWithCapacity:1];
        self.current_xml_value = [NSMutableString stringWithCapacity:80];
        current_element_is_subject = YES;
    }
}
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    if (current_element_is_subject) {
        [current_xml_value appendString:string];
    }
}

-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    if ([elementName isEqualToString:@"subject"]) {
        [item setObject:current_xml_value forKey:@"subject"];
        [previewData addObject:item];
        current_element_is_subject = NO;
//      NSLog(@"array count: %@", [previewData count]);
    }
}

The blog entry "Make NSXMLParser your friend" appears to be doing basically the same thing you want. It would probably be helpful to study it.

1
votes

Make sure you initialize previewData - if you never initialize the array in the first place, it'll be a nil value. In Objective-C, you can send message to nil, so sending the addObject: message will do nothing, and [nil count] will return 0.