2010-11-08 9 views
2

J'ai lu des articles à ce sujet, et il semble assez simple. Je suis assez nouveau pour Obj-C et iPhone dev en général, donc je pourrais facilement négliger quelque chose. Je ne peux pas sembler retourner le NSMutableArray avec les objets article. Je ne reçois aucune erreur, mais quand j'essaie de NSLog() certaines choses, je reçois des erreurs EXEC_BAD_ACCESS (je suppose un problème d'accès à la mémoire?). J'ai une ArticlesParser classe qui l'analyse syntaxique ... Voici à quoi il ressemble:Aide! Comment partager la classe NSXMLParser entre les contrôleurs?

// 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 

est ici la mise en œuvre ...

// 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 

Je sais que l'analyse réelle fonctionne parce que j'ai eu ce directement dans mon contrôleur de vue et tout a bien fonctionné. Mais maintenant, je veux accéder à la même chose à partir d'un autre contrôleur, seule l'URL est différente (renvoie le même format XML).

Voilà comment je suis en train de faire usage de cette classe dans mon contrôleur:

// 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]; 
} 

Y at-il quelque chose d'évident que je suis absent? Est-ce encore un bon moyen de partager le code NSXMLParser? Je tire mes cheveux sur celui-ci ... merci d'avance!

Répondre

1

Qu'est-ce que vous essayez de NSLog qui génère l'erreur de EXEC_BAD_ACCESS? En regardant votre code votre appel à arrayOfArticles devrait retourner un NSMutableArray sans éléments, par exemple. quelque chose comme cela est compréhensible donner un EXEC_BAD_ACCESS:

NSLog(@"%@", [[articles objectAtIndex:0] description]); // index out of bounds 

En ayant votre classe d'analyseur XML également responsable de l'extraction des données qu'il va analyser (en utilisant NSURLConnection) vous avez fait asynchrone, ce qui signifie qu'il est plus apte à être utilisé comme ceci:

ArticlesParser *ap = [[[ArticlesParser alloc] init] autorelease]; 
[ap parseURL:@"http://example.com/foo"]; 
NSArray *anArray = [ap arrayOfArticles]; 

anArray est maintenant un tableau vide, et ne sera peuplée à un moment indéterminé dans l'avenir, le cas échéant - et vous ne pouvez pas détecter le moment venu sans vote le tableau . Urgh! :)

Il y a plusieurs façons de contourner ce problème. Une approche consiste à demander à votre classe XML Parser de déclarer des méthodes déléguées, en proposant des rappels lorsque les données XML ont été entièrement récupérées et analysées et lorsque des conditions d'erreur surviennent (de la même manière que les méthodes déléguées en NSURLConnection). Une autre approche consiste à faire de votre classe XML Parser un simple analyseur XML (synchrone) et à déplacer le code asynchrone de récupération de données vers l'extérieur de votre classe.

+0

Dans cette situation, l'appel synchrone serait bien. Je ne suis pas sûr de savoir comment faire l'appel asynchrone, synchrone cependant. – rpheath

1

Il y a quelques choses que je vois un problème avec le haut de ma tête. D'abord, vous devez soit copy ou retain le retour de arrayOfArticles si vous voulez vous y accrocher et l'utiliser plus tard.

articles = [[aParser arrayOfArticles] copy]; 

Vous devez bien sûr vous assurer de le relâcher plus tard, le cas échéant. Deuxièmement, comme il est écrit loadArticles fuit effectivement le ArticleParser il crée donc vous devez appeler [aParser release] à la fin de la méthode.

Le fait que vous deviez libérer l'analyseur, qui a créé le tableau, est ce qui rend nécessaire de conserver/copier la valeur de retour. Dès que le ArticlesParser est désalloué, il libère son articlesCollection interne et le libère s'il s'agit de la dernière référence. Puisque votre méthode arrayOfArticles distribue cette référence aux autres, ils doivent copier la matrice ou conserver la référence pour la maintenir en vie après que le ArticlesParser qui l'a créée soit mort.

Enfin, vous téléchargez les données de manière asynchrone, mais vous appelez arrayOfArticles immédiatement après avoir appelé parseUrl:. Cela ne vous donnera jamais rien d'utile, car aucune donnée n'a encore été téléchargée ou analysée. Vous avez besoin de votre ArticlesParser pour fournir un moyen de notifier les parties intéressées quand il est fait d'analyser les données téléchargées et ensuite ils peuvent appeler arrayOfArticles pour obtenir les données.

EDIT:

Une façon de faire face à la notification serait de créer un protocole de délégué, ajouter une propriété déléguée à ArticlesParser, lui-même ont le contrôleur défini comme la valeur de cette propriété, et que l'appel de l'analyseur de la méthode du délégué quand c'est fait.

Par exemple:

// ArticlesParser.h 
#import <Foundation/Foundation.h> 
#import "Article.h" 

@class Article; 
@class ArticlesParser; 

@protocol ArticlesParserDelegate <NSObject> { 
    - (void)parserDidFinish:(ArticlesParser*)parser; 
    - (void)parser:(ArticlesParser*)parser didFailWithError:(NSError*)error; 
@end 

@interface ArticlesParser : NSObject <NSXMLParserDelegate> { 
    id<ArticlesParserDelegate> delegate; 
    // ... the rest the same ... 
} 
@property (nonatomic, assign) id<ArticlesParserDelegate> delegate; 
// ... the rest the same ... 
@end 

// ArticlesParser.m 
// ... the same as you have, but with this stuff added ... 
@synthesize delegate; 

- (void)connectionDidFinishLoading:(NSURLConnection *)connection 
{ 
    // ... same as you have, add this at end... 
    [delegate parserDidFinish:self]; 
} 

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 
{ 
    // ... same as you have, add this at end... 
    [delegate parser:self didFailWithError:error]; 
} 
+0

Merci, cela a sérieusement éclairci beaucoup. Je l'ai eu à travailler en utilisant la délégation que vous avez décrite ci-dessus. Impressionnant! – rpheath

+0

Peut-être que vous pourriez marquer la réponse comme acceptée alors. – imaginaryboy

+0

(nouveau pour débordement de pile aussi, en passant par mes questions et réponses précédentes maintenant - désolé à ce sujet) – rpheath

Questions connexes