I’ve read posts about this, and it seems pretty straight-forward. I’m pretty new to Obj-C and iPhone dev in general, so I could easily be overlooking something. I can’t seem to return the NSMutableArray with the Article objects. I don’t get any errors, but when I try to NSLog() some stuff I’m getting EXEC_BAD_ACCESS errors (I’m assuming a memory access issue?). I have an ArticlesParser class that does the parsing… Here’s what it looks like:
// ArticlesParser.h
#import <Foundation/Foundation.h>
#import "Article.h"
@class Article;
@interface ArticlesParser : NSObject <NSXMLParserDelegate> {
NSMutableString *currentCharaters;
Article *currentArticle;
NSMutableArray *articlesCollection;
NSMutableData *xmlData;
NSURLConnection *connectionInProgress;
BOOL connectionHasCompleted;
}
@property (nonatomic, assign) BOOL connectionHasCompleted;
- (void)parseUrl:(NSString *)url;
- (void)beginParsing:(NSURL *)xmlUrl;
- (NSMutableArray *)arrayOfArticles;
@end
Here’s the implementation…
// ArticlesParser.m
#import "ArticlesParser.h"
@implementation ArticlesParser
@synthesize connectionHasCompleted;
#pragma mark -
#pragma mark Parsing methods
- (void)parseUrl:(NSString *)url
{
[self setConnectionHasCompleted:NO];
NSURL *xmlUrl = [NSURL URLWithString:url];
[self beginParsing:xmlUrl];
}
- (void)beginParsing:(NSURL *)xmlUrl
{
[articlesCollection removeAllObjects];
articlesCollection = [[NSMutableArray alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:xmlUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
// clear existing connection if there is one
if (connectionInProgress) {
[connectionInProgress cancel];
[connectionInProgress release];
}
[xmlData release];
xmlData = [[NSMutableData alloc] init];
// asynchronous connection
connectionInProgress = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}
- (NSMutableArray *)arrayOfArticles
{
// NOT RETURNING ANYTHING
return articlesCollection;
}
#pragma mark -
#pragma mark NSXMLParserDelegate methods
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[xmlData appendData:data];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqual:@"article"]) {
currentArticle = [[Article alloc] init];
return;
}
if ([elementName isEqual:@"title"]) {
currentCharaters = [[NSMutableString alloc] init];
return;
}
if ([elementName isEqual:@"last_updated"]) {
currentCharaters = [[NSMutableString alloc] init];
return;
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
[currentCharaters appendString:string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqual:@"article"]) {
[articlesCollection addObject:currentArticle];
[currentArticle release], currentArticle = nil;
return;
}
if ([elementName isEqual:@"title"]) {
[currentArticle setTitle:currentCharaters];
[currentCharaters release], currentCharaters = nil;
return;
}
if ([elementName isEqual:@"last_updated"]) {
[currentArticle setLastModified:currentCharaters];
[currentCharaters release], currentCharaters = nil;
return;
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
[parser setDelegate:self];
[parser parse];
[parser release];
[self setConnectionHasCompleted:YES];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[currentArticle release];
currentArticle = nil;
[currentCharaters release];
currentCharaters = nil;
[articlesCollection release];
articlesCollection = nil;
[connectionInProgress release];
connectionInProgress = nil;
[xmlData release];
xmlData = nil;
NSLog(@"connection failed: %@", [error localizedDescription]);
}
@end
I know that the actual parsing works because I did have this directly in my view controller and everything worked fine. But now I want to access basically the same thing from another controller, only the URL is different (returns the same formatted XML though).
Here’s how I’m trying to make use of this class in my controller:
// instance method called within an articles controller
// that is to load the results in a table view
- (void)loadArticles
{
// (leaving off the URL because it's not important)
NSString *urlToRequest = [NSString stringWithFormat:@"...", [self letterToList]];
ArticlesParser *aParser = [[ArticlesParser alloc] init];
// initiate the parsing
[aParser parseUrl:urlToRequest];
// load up the articles ivar so the tableview can
// make use of it to load its cells
articles = [aParser arrayOfArticles];
}
Is there something obvious that I’m missing? Is this even a good way to share the NSXMLParser code?
I’m pulling my hair out over this one… thanks in advance!
What is it you’re trying to NSLog that generates the EXEC_BAD_ACCESS error? Looking at your code your call to arrayOfArticles should return an NSMutableArray with no elements, so e.g. something like this would understandably give an EXEC_BAD_ACCESS:
By having your XML parser class also responsible for fetching the data it’s going to parse (using NSURLConnection) you’ve made it asynchronous, which means it’s no longer suitable to be used like this:
anArrayis now an empty array, and will only be populated at some indeterminate point in the future, if at all – and you can’t detect when that time comes without polling the array. Urgh! 🙂There are a couple of ways you might get around this. One approach is to have your XML Parser class declare delegate methods, offering callbacks for when the XML data has been fully fetched and parsed and when error conditions occur (in much the same way that the delegate methods in
NSURLConnectionwork). Another approach is to have your XML Parser class be a simple (synchronous) XML parser, and move the asynchronous data-fetching code to outside your class.