iOS8 NSXMLParser crash - nsxmlparser

I had a crash in NSXMLParser
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'NSXMLParser does not support reentrant parsing.'
Here is my code
NSString *wrappedSnippet = [NSString stringWithFormat:#"<html>%#</html>", self.snippet];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:[wrappedSnippet dataUsingEncoding:NSUTF8StringEncoding]];
[parser setDelegate:self];
[parser parse];
app crashes on the last line.
Note, that everything works perfect on iOS7!

iOS8 throws an exception that previous versions caught and handled in the background. see manual As from ios 5 NSXMLParser is thread safe but not reentrant! make sure you aren't calling parse from your NSXMLParser delegate. "Self" in your case.

dispatch_queue_t reentrantAvoidanceQueue = dispatch_queue_create("reentrantAvoidanceQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(reentrantAvoidanceQueue, ^{
NSXMLParser* parser = [[NSXMLParser alloc] initWithData:xml];
[parser setDelegate:self];
if (![parser parse]) {
NSLog(#"There was an error=%# parsing the xml. with data %#", [parser parserError], [[NSString alloc] initWithData:xml encoding: NSASCIIStringEncoding]);
}
[parser release];
});
dispatch_sync(reentrantAvoidanceQueue, ^{ });
Replace your code with above lines, Hope it helps you!

I resolved my problem by dispatching parser in background queue!
NSXMLParser is now threadsafe. However, it is not reentrant on a given thread; don't call -parse on an NSXMLParser from within a delegate callback of another NSXMLParser.
- (void)parseWithCompletion:(ParserHandler)handler {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
self.handler = handler;
[self parse];
});
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.handler) {
self.handler(YES, self.dictionary, nil);
self.handler = nil;
}
});
}

In this issue, that means you could re-call [NSXMLParser parse] function in anything of his delegate.
Sometimes you may call [parser parse] in parserDidEndDocument:
But it will notify you that this's a reentrant error!
So, the solution is, either you could [parser parser] in different queue,
for example, you could do it via calling dispatch_async(dispatch_get_main_queue(), ^{ do in block});
or, you need to adjust your call flow,
make sure that you won't call parse function in delegate.

I had the same problem and wrote a subclass based upon NSXMLParser which handles the case:
class SynchronizedXMLParser: NSXMLParser
{
// shared queue
private static let _serialQueue: NSOperationQueue = {
let queue = NSOperationQueue()
queue.qualityOfService = NSQualityOfService.UserInitiated
// making it serial on purpose in order to avoid
// the "reentrant parsing" issue
queue.maxConcurrentOperationCount = 1
return queue
}()
// instance level convenience accessor
private var _serialQueue: NSOperationQueue
{
get
{
return self.dynamicType._serialQueue
}
}
private weak var _associatedParsingTask: NSBlockOperation?
deinit
{
_associatedParsingTask?.cancel()
}
//MARK: - Overridden
required override init(data: NSData)
{
super.init(data: data)
}
// still unsafe to call within the delegate callbacks
override func parse() -> Bool
{
var parsingResult = false
if (_associatedParsingTask == nil)
{
let parsingTask = NSBlockOperation(block: {() -> Void in
parsingResult = super.parse()
})
_associatedParsingTask = parsingTask
// making it synchronous in order to return the result
// of the super's parse call
_serialQueue.addOperations([parsingTask], waitUntilFinished: true)
}
return parsingResult
}
override func abortParsing()
{
if let parsingTask = _associatedParsingTask
{
parsingTask.cancel()
_associatedParsingTask = nil
}
super.abortParsing()
}
// MARK: - Introduced
// safe to use everywhere as it doesn't force the calling thread to wait until this me
thod returns
func parse(completion completion:(Bool) -> Void) -> Void
{
var parsingResult = false
if (_associatedParsingTask == nil)
{
let parsingTask = NSBlockOperation(block: {() -> Void in
parsingResult = super.parse()
})
parsingTask.completionBlock = { () -> Void in
completion(parsingResult)
}
_associatedParsingTask = parsingTask
// making it synchronous in order to return the result
// of the super's parse call
_serialQueue.addOperation(parsingTask)
}
}
}
P.S. The idea is pretty much the same as what #CrimeZone suggested.

Related

Freezing the UI in IOS

The following code is freezing my UI. Cant do any actions.
- (void) longPoll {
//create an autorelease pool for the thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError* error = nil;
NSURLResponse* response = nil;
NSURL* requestUrl = [NSURL URLWithString:#"myurl"];
NSURLRequest* request = [NSURLRequest requestWithURL:requestUrl];
//send the request (will block until a response comes back)
NSData* responseData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
[self dataReceived:responseData];
});
});
//compose the request
//pass the response on to the handler (can also check for errors here, if you want)
//clear the pool
}
- (void) startPoll {
//not covered in this example: stopping the poll or ensuring that only 1 poll is active at any given time
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
- (void) dataReceived: (NSData*) theData {
//process the response here
NSDictionary *dict=[theData JSONValue];
[self ParseJson:dict];
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
Can anyone give me the exact reason for it or any alternative to do the similar code for continues polling.
You are creating an infinite loop:
longCall calls dataReceived calls longCall etc....
What exactly you want to do. There is infinite loop between longPool and dataReceived
there should be mechanism where you stop this call and you can use
#autorelease {} block for create autorelease pool in ARC Enabled project and
NSAutoReleasePool class obj for Without ARC.

iOS instance variables not initialised from within a block

i am using parse.com as backend for my app.
i need to get information from my backend and init an instance with this information.
i use this code in order to do so:
- (id) initWithTeamId:(NSString *)teamId
{
__block NSString *str;
__block FrFTeam *blockSelf = self;
PFQuery *query = [PFQuery queryWithClassName:#"teams"];
[query getObjectInBackgroundWithId:teamId block:^(PFObject *object, NSError *error) {
str = [object objectForKey:#"teamName"];
(void)[blockSelf initWithName:str players:nil thumb:nil];
}];
return self;
}
when this code is done self.name is set to null,
what am i doing wrong?
thank you!
Try this code:
// call init on the object, then setup the team id
- (id)initWithTeamId:(NSString *)teamId
{
self = [super init];
if (self) {
[self setupWithTeamId:teamId];
}
return self;
}
- (void) setupWithTeamId:(NSString *)teamId
{
__weak FrFTeam *blockSelf = self;
PFQuery *query = [PFQuery queryWithClassName:#"teams"];
[query getObjectInBackgroundWithId:teamId block:^(PFObject *object, NSError *error) {
NSString *name = [object objectForKey:#"teamName"];
NSLog(#"Received name: %# from object: %#", name, object);
[blockSelf setName:name];
}];
}
Then, change the name of the method from initWithName:... because this isn't really an init method because you have already done the init before calling setupWithTeamId:.
If you need the parse bit to be done before the init method returns, you should:
Call parse to get the details before calling init on the object
Use getObjectWithId: --- not recommended as this blocks the thread in init, bad idea
Pretty sure the reason is in the method name you are calling -getObjectInBackgroundWithId:block: (it specifies InBackground, which suggests the block is called at some later stage and not immediately)
This would suggest that you end up with self == nil (as you are not calling any other initialiser in the method.
Initialisation of an object has to be synchronous.

Pass argument to a block?

I have a singleton that I'm using to parse XML and then cache it. The parsing/caching is done with a block. Is there any way for me to pass an argument to this block from another class so that I can change the URL from outside the singleton?
Here's the code I have now:
// The singleton
+ (FeedStore *)sharedStore
{
static FeedStore *feedStore = nil;
if(!feedStore)
feedStore = [[FeedStore alloc] init];
return feedStore;
}
- (RSSChannel *)fetchRSSFeedWithCompletion:(void (^)(RSSChannel *obj, NSError *err))block
{
NSURL *url = [NSURL URLWithString:#"http://www.test.com/test.xml"];
...
return cachedChannel;
}
And here's the class where I need to modify the NSURL from:
- (void)fetchEntries
{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
// Initiate the request...
channel = [[BNRFeedStore sharedStore] fetchRSSFeedWithCompletion:
^(RSSChannel *obj, NSError *err) {
...
}
}
How do I pass an argument from fetchEntries to fetchRSSFeedWithCompletion?
You would want to add a parameter in the method, not the block.
Also, when using a completion block, there really is no reason to return anything in the method.
I'd change it to look like this:
-(void)fetchRSSFeed:(NSURL *)rssURL completion:(void (^)(RSSChannel *obj, NSError *error))block{
RSSChannel *cachedChannel = nil;
NSError *error = nil;
// Do the xml work that either gets you a RSSChannel or an error
// run the completion block at the end rather than returning anything
completion(cachedChannel, error);
}

exc_bad_access (code=2, address=0x20) in xmlParser

I am using xml parser in my application. When i run my application for 10 to 15 time its works fine but suddenly its giving me the bad_access with above codes. My xml parser code as follow:
-(BOOL)getTheServerStatus:(NSData *)webData
{
if (webData==NULL)
{
return FALSE;
}
parser=[[NSXMLParser alloc]initWithData:webData];
[parser setDelegate:self];
[self performSelectorOnMainThread:#selector(parseData:)
withObject:webData
waitUntilDone:YES];
if([strVal isEqualToString:#"ok"])
{
return TRUE;
}
else
{
return FALSE;
}
}
- (void)parseData:(NSData *)webData
{
if(webData==NULL)
{
NSLog(#"web data is NULL");
}
[parser parse];
}
I am using automatic reference counting .So what is the issue with my code?
I assume that getTheServerStatus is getting called on a thread which is NOT the main thread. Yet you do the parsing on the main thread. Is it possible a second thread us clobbering the parser?
EDIT: code changed
So what you should do is NOT block in getTheServerStatus:, but break your problem into two parts. The first is you want to get the status - so you are going to dispatch a block to do that work for you. While that is going on you can throw up a spinner, or just disable some of your UI. That is a design decision of course. When the background thread looking for status is done, it will message you back on the main thread with the result, and you can take whatever action you want then. I just posted an answer to a similar question that has even more code, which you might find helpful.
{ // ivars
NSXMLParser *parser; // so you can send it abortParsing to cancel the background block
}
-(void)getTheServerStatus:(NSData *)webData
{
if (webData==nil) // nil is for objects, NULL for pointers
{
dispatch_async(dispatch_get_main_queue(), ^{ [self parseResult:NO]; } );
}
parser=[[NSXMLParser alloc]initWithData:webData];
[parser setDelegate:self];
dispatch_async(dispatch_get_global_queue(0,0), ^
{
BOOL ret = [parser parse];
parser = nil;
if(ret == YES) {
ret = [strVal isEqualToString:#"ok"]; // EDIT
}
dispatch_async(dispatch_get_main_queue(), ^{ [self parseResult:ret]; } );
} );
}
-(void)parserResult:(BOOL)retCode
{
// now on main thread...
if(retCode == YES) ....
else .....
}

NSOperationQueue and ASIHTTPRequest

I'm writing test cases for a wrapper class written around ASIHTTPRequest. For reasons I can't determine, my test cases complete with failure before the ASIHTTPRequest finishes.
Here's how the program flow works.
Start in my test case.
Init my http engine object, instruct it to create a new list
Create the new ASIHTTPRequest object and set it up.
Add the request to an operation queue.
Wait until that queue is empty
Check to see if my delegate methods were called and fail the test if they weren't.
Now, most of the time everything works fine and the test passes, but some of the time it fails because my delegate methods were called AFTER the operation queue returned control to my wait method.
Test Case
// Set my flags to 'NO'
- (void)setUp {
requestDidFinish = NO;
requestDidFail = NO;
}
- (void)testCreateList {
NSString *testList = #"{\"title\": \"This is a list\"}";
JKEngine *engine = [[JKEngine alloc] initWithDelegate:self];
NSString *requestIdentifier = [engine createList:jsonString];
[self waitUntilEngineDone:engine];
NSString *responseString = responseString_;
[engine release];
GHAssertNotNil(requestIdentifier, nil);
GHAssertTrue(requestDidFinish, nil);
GHAssertTrue([responseString hasPrefix:#"{\"CreateOrEditListResult\""], nil);
}
// Puts the test into a holding pattern until the http request is done
- (void)waitUntilEngineDone:(JKEngine *)engine {
[engine waitUntilFinishedRunning];
}
// The delegate method called on successful completion
- (void)requestFinished:(NSString *)requestIdentifier withResponse:(NSString *)response {
NSLog(#"request did finish");
requestDidFinish = YES;
responseIdentifier_ = [requestIdentifier retain];
responseString_ = [response retain];
}
Engine Code
- (NSString *)createList:(NSString *)list {
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:url]];
[request addRequestHeader:#"Content-Type" value:kContentType];
[request setRequestMethod:kPOST];
request.delegate = self;
[request appendPostData:[list dataUsingEncoding:NSUTF8StringEncoding]];
NSString *requestIdentifier = [NSString stringWithNewUUID];
[operationQueue_ addOperation:request];
[operationDictionary_ setObject:request forKey:requestIdentifier];
return requestIdentifier;
}
// This is the ASIHTTPRequest delegate method that's called on success
// but it sometimes isn't called until AFTER the operationQueue finishes running
- (void)requestFinished:(ASIHTTPRequest *)request {
DLog([request responseString]);
BOOL canNotifiyDelegate = [self.delegate respondsToSelector:#selector(requestFinished:withResponse:)];
if (canNotifiyDelegate) {
NSArray *keyArray = [operationDictionary_ allKeysForObject:request];
NSString *requestIdentifier = [keyArray objectAtIndex:0];
[operationDictionary_ removeObjectForKey:requestIdentifier];
if ([keyArray count] != 1) {
ALog(#"It looks like a request was added to the operation dictionary multiple times. There's a bug somewhere.", nil);
}
[self.delegate requestFinished:requestIdentifier withResponse:[request responseString]];
}
}
- (void)waitUntilFinishedRunning {
[operationQueue_ waitUntilAllOperationsAreFinished];
}
This is the way ASIHTTPRequest works. Delegate methods are called on the main thread, and calls to delegates do not block the request thread, so it's perfectly possible your delegates will be called after the queue finishes.
ASIHTTPRequest calls delegate methods on the main thread, by default GH-Unit runs its tests on a background thread. I'm still a little hazy on exactly what was going on, but forcing my network tests to run on the main thread fixed the problem.
I implemented the following method in my network test class.
- (BOOL)shouldRunOnMainThread {
return YES;
}