2
votes

I am using NSOperationQueue & NSInvocationOperation to call a method on my UIViewController which then calls a method in another class which, in turn, parses an XML file from the web and updates some Core Data Managed Objects.

This process works when I run the application first, but when I call the method for the second time (when the UIViewController is shown again), it doesn't execute any lines after the executeFetchRequest statement in the XML parser classs. Here's the code I am using to execute:

I have a UITableViewController that takes all the static information for the carparks from my Core Data store when it loads:

- (void)viewDidLoad
{
    NSLog(@"viewDidLoad");
    [super viewDidLoad];

    southeastCarparks = [[NSMutableArray alloc] init];
    southwestCarparks = [[NSMutableArray alloc] init];
    northeastCarparks = [[NSMutableArray alloc] init];
    northwestCarparks = [[NSMutableArray alloc] init];
    carparkLocations = [[NSMutableArray alloc] initWithObjects:southeastCarparks, southwestCarparks, northeastCarparks, northwestCarparks, nil];


    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription
                               entityForName:@"CarparkInfo" inManagedObjectContext:self.managedObjectContext];


    [fetchRequest setEntity:entity];
    NSError *error;
    self.carparkInfos = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];

    for (CarparkInfo *carpark in self.carparkInfos){
        if ([carpark.details.region isEqualToString:@"Southeast"]){
            [southeastCarparks addObject:carpark];
        } else if ([carpark.details.region isEqualToString:@"Southwest"]){
            [southwestCarparks addObject:carpark];
        } else if ([carpark.details.region isEqualToString:@"Northeast"]){
            [northeastCarparks addObject:carpark];
        } else if ([carpark.details.region isEqualToString:@"Northwest"]){
            [northwestCarparks addObject:carpark];
        }
    }

    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(loadXMLData)
                                             name:@"appDidBecomeActive"
                                           object:nil];
}

Then when the view appears I call the loadXML method to go to the web and get the latest parking space information from an xml file

- (void) viewDidAppear:(BOOL)animated
{
    [self loadXMLData];
    NSLog(@"viewDidAppear");
}


- (void) loadXMLData {
    NSLog(@"loadXMLData");

    NSOperationQueue *queue = [NSOperationQueue new];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                        selector:@selector(loadXMLDataWithOperation)
                                                                          object:nil];
    [queue addOperation:operation];
}

- (void) loadXMLDataWithOperation {
    xmlParser = [[XMLParser alloc] loadXMLByURL:xmlFileURL];

    [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];
}

In the XMLParser class, I take the xmlFileURL passed in, retrieve it from the web, parse it and then attempt to update the available spaces in the Core Data store

-(id) loadXMLByURL:(NSString *)urlString
{
    _carparks = [[NSMutableArray alloc] init];
    NSURL *url = [NSURL URLWithString:urlString];
    NSData *data = [[NSData alloc] initWithContentsOfURL:url];
    parser = [[NSXMLParser alloc] initWithData:data];
    parser.delegate = self;
    [parser parse];
    return self;
}

- (void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
    if (![elementName isEqualToString:@"carpark"]){
        return;
    }

    currentCarpark = [Carpark alloc];

    NSString *name = [attributeDict objectForKey:@"name"];
    currentCarpark.name = name;

    NSString *spaces = [attributeDict objectForKey:@"spaces"];
    currentCarpark.spaces = spaces;

    [self.carparks addObject:currentCarpark];
    currentCarpark = nil; 

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

    // set up the managedObjectContext to read data from CoreData
    id delegate = [[UIApplication sharedApplication] delegate];
    self.managedObjectContext = [delegate managedObjectContext];

    NSEntityDescription *entity = [NSEntityDescription
                               entityForName:@"CarparkInfo" inManagedObjectContext:self.managedObjectContext];

    [fetchRequest setEntity:entity];
    [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"code=%@",name]];

    NSError *error;
    CarparkInfo *cgCarpark;

    // THIS IS THE LAST LINE TO BE EXECUTED THE 2ND TIME AROUND. THE APPLICATION DOESN'T THROW AN ERROR BUT DOESN'T GO ON TO UPDATE THE CARPARK OBJECT ON THE LINE AFTER
    cgCarpark = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];

    cgCarpark.availableSpaces = spaces;
    error = nil;
    if (![self.managedObjectContext save:&error]) {
        //Handle any error with the saving of the context
    }
}

Am I doing something wrong in the way I am using the Managed Object Context?

Thanks in advance for any help

2
I think you will havwe to show how this block of code gets called. - Mike D
Seems that you have a deadlock in this line. Please, give us a context in which you are running this method. - Mark Kryzhanouski
i've updated the code to show how the method is being called. is there any way to check whether a deadlock is occurring? - cullener
i added the logging and noticed that loadXMLData is being called twice when the view starts up: once by the addition of the NSNotification and once in viewDidAppear, so i need to figure out an alternative to creating the NSNotification in viewDidLoad. - cullener

2 Answers

3
votes

I think @Mark_Kryzhanouski is correct and you have a deadlock. Probably due to the fact that you are accessing your NSManagedObjectContext on multiple threads. You need to be careful with your threads in Core data. Your NSOperation will be running on a background thread so those calls to your NSManagedObjectContext will not be on the same thread as the first call in viewDidLoad. Here is some reference material for Core Data concurrency...

Concurrency with Core Data and Core Data Release Notes for OS X v10.7 and iOS 5.0

There is also a good WWDC presentation on the topic in the WWDC 2012 videos, "Session 214 - Core Data Best Practices".

1
votes

Just write the following statement before fetching your result controller

self.fetchedResultsController = nil;