j. Doe
j. Doe

Reputation: 33

Getting nil from property

I have 2 classes, NSObject class DataParser and a ViewController. In DataParser class I have some methods that parses XML and results are saved into an array which is a property of DataParser class. I want to show results to user in my ViewController. I have here this property @property (strong, nonatomic) DataParser *parser; and in viewWillAppear method I created instance of DataParser class and I'm trying to get array with results this way

self.parser = [[DataParser alloc] init];
[self.parser downloadBooksXML];
NSLog(@"%@", self.parser.array);

but I'm still getting nil. Does anyone know where could be the problem?

- (void)downloadBooksXML
{
    NSURL *url = [NSURL URLWithString:@"http://..."];

    [self downloadDataFromURL:url withCompletionHandler:^(NSData *data) {
        if (data) {
            self.xmlParser = [[NSXMLParser alloc] initWithData:data];
            self.xmlParser.delegate = self;
            self.foundValue = [[NSMutableString alloc] init];
            [self.xmlParser parse];
        } else {
            NSLog(@"Error");
        }
    }];
}

EDIT: When I nslog array in DataParser class in parserDidEndDocument method, it contains data

Upvotes: 3

Views: 65

Answers (3)

CSmith
CSmith

Reputation: 13458

Given that you're likely setting your DataParser array property somewhere in your XML parser, and that downloadBooksXML is performing the download and parsing asynchronously, you will find a nil value immediately upon return of the call to downloadBooksXML.

The delegation pattern suggested by @Aaron works. You could also consider adding your own completion handler to your downloadBooksXML method, something like this:

typedef void (^DownloadBooksCompletionHandler)();

- (void)downloadBooksXMLWithCompletionHandler:(DownloadBooksCompletionHandler)completionHandler
{
    NSURL *url = [NSURL URLWithString:@"http://..."];

    [self downloadDataFromURL:url withCompletionHandler:^(NSData *data) {
        if (data) {
            self.xmlParser = [[NSXMLParser alloc] initWithData:data];
            self.xmlParser.delegate = self;
            self.foundValue = [[NSMutableString alloc] init];
            [self.xmlParser parse];
        } else {
            NSLog(@"Error");
        }

        completionHandler();         // <-- call the completionHandler!
    }];
}

then you can "notify" the caller when the download is complete by calling completionHandler(), and access the array and update your UI.

[self.parser downloadBooksXMLWithCompletionHandler:^()
{
  // now OK to access array property and update your UI
}];

you might also consider passing back an NSError * to the completion handler as a means of passing error info.

Lastly, beware of threading issues...completionHandler() will run on the thread called by your downloadDataFromURL completion handler. Depending on your implementation, this may or may not be the UI thread.

Upvotes: 1

Todor Brachkov
Todor Brachkov

Reputation: 219

NSXMLParser is XML parser (SAX type parser). It starts reading your XML from the beginning, and every time it finds a new element, a closing element or character data, it informs you about it. When parsing your example XML it will call it's delegates:

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
  if (elementName == @"YourElement") {
    // save the elements of attributeDict in your array! This is the place where you add the object to your property self.parser.array!
  }
}

PS: If the guy who voted (-1) can tell me what was wrong in my answer?

Upvotes: -1

Aaron Brager
Aaron Brager

Reputation: 66234

When [self.parser downloadBooksXML] returns, self.parser.array has not been set yet, because the data has not been downloaded and parsed.

The data will be available once the document is done parsing, which you can check using the delegate method:

- (void)parserDidEndDocument:(NSXMLParser *)parser {
    NSLog(@"%@", self.parser.array);
}

You can therefore use a pattern like this:

- (void) viewDidLoad {
    [super viewDidLoad];
    self.parser = [[DataParser alloc] init];
    [self.parser downloadBooksXML];
}

- (void) dataFinished {
    NSLog(@"%@", self.parser.array);
    // update the user interface
}

- (void)parserDidEndDocument:(NSXMLParser *)parser {
   [self dataFinished];
}

Depending on the implementation of your other code, you might need to switch to the main queue to update the UI:

- (void) dataFinished {
    NSLog(@"%@", self.parser.array);

    dispatch_async(dispatch_get_main_queue(), ^(void){
        // update the user interface
    });
}

Upvotes: 3

Related Questions