views:

147

answers:

2

I have a viewController which imports XMLParser.h as the class xmlParser

I'm passing an NSURL object in my viewController to the xmlParser class with the getXML method below

goButton is the button I tap to call the getXML method below. I disable the button which I tapped to trigger the getXML method, but I'm not sure where to put the code to enable it again once the xmlParser has finished parsing the returned XML.

- (IBAction) getXML {
    goButton.enabled = NO;

    // allocate and initialize the xmlParser 
    xmlParser = [[XMLParser alloc] init];

// then generate the URL we are going to pass to it and call the fetchXML method passing the URL.
    NSURL *xmlurl = [[NSURL alloc] initWithString:@"http://www.mysite.com/myfile.xml"];
    [xmlParser fetchXMLFromURL:xmlurl];

    // release objects
    [xmlurl release];
    [xmlParser release];

}

As per @Squeegy recommendation, I modified my code.

- (IBAction) getXML {
    goButton.enabled = NO;

    xmlParser = [[XMLParser alloc] init];

    [self performSelectorInBackground:@selector(parseInBackground:) withObject:xmlParser];

}

- (void)parseInBackground:(XMLParser*)parser {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSURL *xmlurl = [[NSURL alloc] initWithString:@"http://www.mysite.com/myfile.xml"];

    [parser fetchXMLFromURL:xmlurl];
    [self performSelectorOnMainThread:@selector(didFinishXMLParsing:) withObject:parser];

    [xmlurl release];
    [pool drain];

}

- (void)didFinishXMLParsing:(NSXMLParser*)parser {
    goButton.enabled = YES;
}

Looks to be working until it gets to the line

[self performSelectorOnMainThread:@selector(didFinishXMLParsing:) withObject:parser];

The compiler complains as follows:

2010-02-17 00:22:20.574 XMLApp[2443:521b] *** -[viewController performSelectorOnMainThread:withObject:]: unrecognized selector sent to instance 0x1285a0
2010-02-17 00:22:20.578 XMLApp[2443:521b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[viewController performSelectorOnMainThread:withObject:]: unrecognized selector sent to instance 0x1285a0'
2010-02-17 00:22:20.583 XMLApp[2443:521b] Stack: (
    861696817,
    860329709,
    861700631,
    861203093,
    861166272,
    18715,
    846004025,
    845672609,
    848189713
)
A: 

When the parser finishes parsing, it will call it's delegate's:

- (void)parserDidEndDocument:(NSXMLParser *)parser

In that method, you can re-enable the button. You should probably do so with a performSelectorInMainThread call, since it involves changing a view.

pgb
@pgb can you elaborate on how this would be implemented? I'm not changing view, `viewController` is my view which calls the parser. The parser is not in another view, it's just in an external class which is an `NSObject`
Griffo
+1  A: 
- (IBAction)getXML {
    goButton.enabled = NO;

    xmlParser = [[XMLParser alloc] init];
    NSURL *xmlurl = [[NSURL alloc] initWithString:@"http://www.mysite.com/myfile.xml"];
    [xmlParser fetchXMLFromURL:xmlurl];

    [self performSelectorInBackground:@selector(parseInBackground) withObject:xmlParser];

    [xmlurl release];
    [xmlParser release];
}

- (void)parseInBackground:(NSXMLParser*)parser {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    [parser parse];
    [self performSelectorOnMainThread:@selector(didFinishXMLParsing:)
                           withObject:parser
                        waitUntilDone:NO];

    [pool drain];
}

- (void)didFinishXMLParsing:(NSXMLParser*)parser {
    goButton.enabled = YES;
}

The trick is to do the processing on a background thread, which allows the UI to do stuff. When parsing is done, you have to make any UI changes back on the main thread.

Squeegy
Thanks @Squeegy but I think you misunderstood my question but I can't be sure as I don't quite understand your code. My parsing code is in an external class whose sole purpose is to be a generic class I can import for parsing in my apps. I have imported my parser class' header file in my view controller and I call the parser from within my viewController.
Griffo
@Squeegy my objective is just to disable the button so the method can't be called multiple times before it's even finished the previous request. So I just want the user to be prevented from pressing the button until the parsing has completed.
Griffo
This does that. `performSelectorInBackground:withObject:` allows you to run a method in a background thread. This is important to allow your UI on the main thread to update (disabling the button). Your app is responsive, but your button is disabled. When parsing is done, it says "Hey main thread, i'm done with parsing", and the main thread enables the button. This is important because *only* the main thread can update the UI. So your parser class would execute it's own parsing methods in a background thread, and then call a method of it's delegate on the main thread when it's done.
Squeegy
OK, I see what you're saying I think. I'm getting a runtime error, see below: 2010-02-16 23:54:31.935 XMLApp[2371:640f] *** -[XMLParser parse]: unrecognized selector sent to instance 0x152340The object being passed to parseInBackground is a type XMLParser which is my custom class. It's not an NSXMLParser
Griffo
`NSXMLParser` has a method called `parse`. You need to call the method that does the parsing in your custom class. `fetchXMLFromURL:` I suppose it would be.
Squeegy
Please see my edit @Squeegy thanks
Griffo
Fixed my answer to call the right method, my bad. But when in doubt about these things, look up methods in the docs. That would have cleared this one up pretty easily.
Squeegy
Also, I just made up these methods, but you can use whatever you want. You dont need to pass the xmlParser object around if you don't need it. In that case the selectors you call have no argument, and the `withObject:` argument becomes simply `nil`.
Squeegy