I am new to iPhone/Objective-C development, I am successfully parsing XML with NSXMLParser but I can't get exceptions to work properly. I'd like to use exceptions to deal with unexpected XML.
I'm wrapping the code for creating the NSXMLParser object and sending the setDelegate and parse messages to the object inside a #try #catch block, catching #NSException.
If I put NSAssert(FALSE, #"error) inside the #try block, the exception is caught properly. If, however, I have an NSAssert fail inside the delegate calls (eg, didStartElement, didEndElement, foundCharacters), then the program dies (in iPhone Simulator, haven't tried device yet). The debugger stack trace shows the assertion was raised into an exception but it doesn't pull back out into the top level code where the #try block is around the [parser parse] message call. Instead I get "Terminating app due to uncaught exception."
Please let me know if this is a known problem or if I'm doing something silly here.
Thanks -- Alex
Some code to make more concrete; no attempt to make this code correct for memory/releases/etc.
#implementation XMLTester
+(void)runXMLTester
{
BOOL success = FALSE;
XMLTester *tester = [[XMLTester alloc] init];
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=KSFO"]];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:tester];
#try {
//NSAssert(FALSE, #"error"); // this assertion works fine
success = [parser parse];
}
#catch (NSException * e) {
success = FALSE;
NSLog(#"Exception caught %#: %#", [e name], [e reason]);
}
#finally {
NSLog(#"runXMLTester #finally block hit");
}
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
NSLog(#"Starting element %#", elementName);
NSAssert(FALSE, #"error"); // this assertion does not work - does not hit #try block around parse message
}
According to Bill Bumgarner, catching exceptions in the iPhone Simulator doesn't work correctly. Your best bet is to stop using exceptions here, as it's not really appropriate anyway. You should be calling -[NSXMLParser abortParsing] instead.
Don't use exceptions for flow control. Writing exception-safe (refcounted) Obj-C code is a bit of a pain — in particular, commonly-used stuff like Foo * foo = [[Foo alloc] init]; [foo doStuff]; [foo release]; foo = nil; will leak and [foo lock]; [foo doStuff]; [foo unlock]; will probably deadlock. You can mitigate the former by always autoreleasing immediately (I always do to prevent memory leaks when refactoring code), except you can't autorelease autorelease pools. The latter is hard to avoid unless you sprinkle #try/#finally everywhere.
Additionally, I strongly recommend breakpointing objc_exception_throw(). Sometimes Xcode seems to miss the throw and drop you into the debugger when abort() is called from uncaught_exception_handler() (or whatever it's called) after the stack's been unhelpfully unwound. And several things (notably CoreAnimation) catch, log, and otherwise ignore exceptions, which is a pain to debug unless you're watching the long.
There's one case in an app where I used an exception for control flow (I think I gave it the name "ControlThrow"); every time I hit that breakpoint I'm tempted to replace it with a goto.
Related
I developed a simple RSS fetcher application that parses an XML RSS feed from a programmer-provided website and displays the articles in the Master View Controller and the UIWebView in a Detail View Controller. I custom set-up a personal server to render the RSS article selected in the master table view (for certain reasons) as a PDF. However, this obviously takes some time on the server end, unless the selected table view cell has already been rendered as a PDF on the server end. My server and blog don't talk to one another (and can't, for other reasons), so I can't pre-render the PDFs as I create the blog posts. The PDF render must be done from the application itself.
I decided to use Grand Central Dispatch to create a separate thread to talk to the server and render the PDFs before the user can select an arbitrary cell to see the post. Here is the code I used to create the queue.
dispatch_queue_t networkQueue = dispatch_queue_create("com.company.networkQueue", NULL);
...and the code I used to create the new thread...
dispatch_async(networkQueue, ^{ [self cachePDFRequests]; });
...here is my cachePDFRequests method that is called in the block request...
- (void) cachePDFRequests {
NSURL *myURL;
NSString *cacheUrl;
NSURLRequest *request;
for (int i = 0; i <= feeds.count; i++) {
cacheUrl = [feeds[i] objectForKey:#"link"];
cacheUrl = [cacheUrl stringByReplacingOccurrencesOfString:#" " withString:#""];
cacheUrl = [cacheUrl stringByReplacingOccurrencesOfString:#"\n" withString:#""];
NSString *fullUrl = [NSString stringWithFormat:#"http://myserver.com/render.php?url=%#", cacheUrl];
myURL = [NSURL URLWithString:fullUrl];
request = [NSURLRequest requestWithURL:myURL];
[cacheView loadRequest:request];
}
}
Note: cacheView is a UIWebView that is not on any UI...it is just an ivar of my Master VC class.
So, when I run the dispatch_async() function in -[viewDidLoad], it runs the -[cachePDFRequests] method and the for() loop inside of it, then throws SIGABRT on my newly-created thread. Please ask any questions as necessary. Let me know if I need to include any code that I haven't already provided.
Here is a picture of the SIGABRT error that appears whenever I run the GCD thread:
Thanks in advance!
P.S. I used to run cacheView = [[UIWebView alloc] init]; in an if() loop if the cacheView was set to nil...that used to throw the SIGABRT error. Now, it has no references to code in my Master View Controller after I removed that line.
EDIT: Here is the code for what the feeds mutable array is containing:
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:#"item"]) {
[item setObject:title forKey:#"title"];
[item setObject:link forKey:#"link"];
[feeds addObject:[item copy]];
}
It looks like you are accessing feeds out of bounds. NSArray will throw an exception if you try to access it with an index that is outside of its bounds, and that is exactly what the backtrace indicates. This is the correct way to loop over an array with indices starting with 0:
for (int i = 0; i < feeds.count; i++) // Notice that it uses < instead of <= as comparator
As a side note, there is no such thing as an if() loop.
I use SOAP to get Time from website, when executes the following souce code, it raises problem.
I guess the line 4 at below source code has problem, but I don't know how to fix, please help.
Thanks.
Source Code:
-(void) parse:(NSXMLParser *)parser didEndElement:(NSString *) elementName namespaceURI:(NSString *)namespceURI qualifiedName:(NSString *)qName {
if ([elementname isEqualToString:#"getOffesetUTCTimeResult"])
{
greeting.text = [[[NSString init] strinWithFormat:#"The local time is:", nameInput.text] stringByAppending:soapResults];
[soapResults release];
soapResults = nil;
}
}
Raised error:
*** Termination app due to uncaught exception 'NSInvalidArgumentException', reason: '** +[NSString <0x267fd8> init]: cnnot init a class object.'
*** First throw call stack:
...
terminate called throwing an exception
You cannot call the initializer without allocing memory for objects. For NSString, you can do something like:
greeting.text = [[NSString stringWithFormat:#"The local time is:", nameInput.text] stringByAppending:soapResults];
You don't have to init NSString.
Just use
greeting.text = [[NSString strinWithFormat:#"The local time is:", nameInput.text] stringByAppending:soapResults];
OK. I'm using NSXMLParser like so:
myParser = [[[BMLT_Parser alloc] initWithContentsOfURL:[NSURL URLWithString:uri]] retain];
[myParser setDelegate:self];
[myParser performSelectorInBackground:#selector(parse) withObject:nil];
I have my own subclass in order to do things like have a memory pool and some instance data. Basically, it's OK to think of BMLT_Parser as the same as NSXMLParser.
Note that I am calling it asynchronously, so there's no simple deallocation after a synchronous call.
What I have done, is declare the following delegate function:
- (void)parserDidEndDocument:(NSXMLParser *)parser ///< The parser in question
{
[myParser release];
myParser = nil;
}
myParser is an instance variable of the parser. Basically, myParser == parser in the callback.
Now, the problem is that Instruments tells me that the parser is leaking. Parsers leak badly, because they pack a lot of luggage.
How else should I dealloc asych parsers? I strongly suspect that I simply need to be directed to an "M", so that I can "RTFM".
Thanks!
myParser = [[[BMLT_Parser alloc] initWithContentsOfURL:[NSURL URLWithString:uri]] retain];
In the above code, u r firstly allocating memory for myParser by alloc, and again u r retaining .Here, u r doing wrong as u should retain only when u have take ownership of an object.But through alloc u will get ownership of the object"myParser".
And when u have used the object, u need to release that.
you should do like something this..
myParser = [[BMLT_Parser alloc] initWithContentsOfURL:[NSURL URLWithString:uri]];
[myParser setDelegate:self];
[myParser performSelectorInBackground:#selector(parse) withObject:nil];
[myParser release];
Again, in the delegate definition, you r firstly releasing the object then setting that to nil.This is quite meaningless ,as if u don't have memory for any object, how can we set any value to that. write something like this..
- (void)parserDidEndDocument:(NSXMLParser *)parser ///< The parser in question
{
if(_myParser)
{
[_myParser release];
}
}
Anyone else experiencing crashes deep down in the iPhone libraries when NSXMLParser parses an xml containing errors? I thought it was supposed to call:
(void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
but instead it crashes the entire app somewhere inside _xmlRaiseError.
Is anyone else experiencing this and is there a way to catch this, instead of having my program crash?
The error handling is not found in the TouchXML framework or in the CXMLDocument. It is in the libxml framework which will (to my knowledge) output a string but not raise an exception. So it is all about passing an error pointer and then reading it straight after. If it is not nil, an error has occurred. If you are getting crashes the error should be somewhere else... Hope that helps.
You should be able to use #try/#catch to wrap this if you need to handle all kinds of malformed XML.
The XML parser never crashes for me, but my handlers have crashed on occasion. For example if I get < foo /> and try to store the value of it in an array (nil, boom). The following is the exact code I use, which parses XML using a delegate that I've made.
NSData *data = [[NSData alloc] initWithContentsOfFile:filename];
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:data];
MGXMLParser *parser = [[MGXMLParser alloc] initWithRecipient:self];
[xmlParser setDelegate:parser];
BOOL success = [xmlParser parse];
if (success) {
NSLog(#"No errors");
} else {
NSError *error = [xmlParser parserError];
NSLog(#"Errors with xmlParser: %#", [error localizedDescription]);
}
[parser release];
[xmlParser release];
[data release];
MGXMLParser is my own class which is a delegate for the XML parser, in case it wasn't obvious.
Update: oops, SO parsed my < foo/ > into nothingness.
The problem is probably that your XML string gets autoreleased before parseErrorOccurred ever gets called, causing a call to a dealloc'd object.
The solution is do to something like:
NSData *data = [[contentString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES] retain]; //use a retain, to stop data being autoreleased
NSXMLParser* xmlParse = [[NSXMLParser alloc] initWithData:data];
[xmlParse setDelegate:self];
[xmlParse parse];
[data release]; //now release data
[xmlParse release];
I filed this as a bug report and Apple answered me 1 year later to say it should be fixed in iOS5.
I have a strange issue, when it comes to parsing XML with NSXMLParser on the iPhone. When starting the app, I want to preload 4 table-views, that are populated by RSS-Feeds in the background.
When I init the table-views one-by-one, than loading, parsing and displaying all works like a charm. But when I try to init all view at once (at the same time), than it seems, that the XML-parser-instances are disturbing each other. Somehow data from one XML-Feed are "broadcasted" into other xml-parser instances, where they do not belong. Example: there is a "teammember" item, with "This is my name". When this bug occurs, there is a string from another xml-feed added, i.e. resulting in: "This is my name58", where 58 is the chart-position of something from the other view. "58" seems to miss then on the other instance.
It looks to me, that this bug occurs because of the NSXMLParser-delegate method:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (!currentStringValue) {
currentStringValue = [[NSMutableString alloc] initWithCapacity:50];
}
[currentStringValue appendString:string];
}
In this case "by coincidence" bytes are appended to strings, where they do not belong to.
The strange thing is, that every instance of NSXMLParser is unique, got its own unique delegates, that are attached to their own ViewController. Every parsing-requests spawns it own background-task, with its own (also also unique named) Autorelease-pool.
I am calling the NSXMLParser like this in the ViewController:
// prepare XML saving and parsing
currentStringValue = [[[NSMutableString alloc] initWithCapacity:50] retain];
charts = [[NSMutableArray alloc] init];
NSURL *url = [[NSURL alloc] initWithString:#"http://(SOME XML URL)"];
xmlParser = [[[NSXMLParser alloc] initWithContentsOfURL:url] retain];
//Set delegate
[xmlParser setDelegate:self];
//loading indicator
progressWheel = [[[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(150.0,170.0,20.0,20.0)] autorelease];
progressWheel.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
[self.view addSubview:progressWheel];
[progressWheel startAnimating];
// start loading and parsing the xml-feed in the background
//[self performSelectorInBackground:#selector(parse:) withObject:xmlParser]; -> I also tried this
[NSThread detachNewThreadSelector:#selector(parse:) toTarget:self withObject:xmlParser];
And this is one of the background-tasks, parsing the feed:
-(void)parse:(NSXMLParser*)myParser {
NSAutoreleasePool *schedulePool = [[NSAutoreleasePool alloc] init];
BOOL success = [myParser parse];
if(success) {
NSLog(#"No Errors. xmlParser got: %#", myParser);
(POST-PROCESSING DETAILS OF THE DATA RETURNED)
[self.tableView reloadData];
} else {
NSLog(#"Couldn't initalize XMLparser");
}
[progressWheel stopAnimating];
[schedulePool drain];
[myParser release];
}
What could cause this issue? Am I calling the background-task in the right way? Why is this bug approaching, since every XML-Parser got its own, unique instance?
You should not be updating UI elements (like progressWheel) from inside a background thread. UI updates should be done on the main thread.
Use -performSelectorOnMainThread:withObject:waitUntilDone: to update UI elements from within a background thread.
I've released an open source RSS/Atom Parser for iPhone and it makes reading and parsing web feeds extremely easy.
You can set it to download the data asynchronously, or you could run it in a background thread synchronously to collect the feed data.
Hope this helps!