Domanda

I am new to dispatch_queue's and have ran into a problem trying to save to CoreData in the background. I have read the CoreData programming guide and I am creating a separate NSManagedObjectContext while in the background thread. When I do a simple loop to create NSManagedObjects in a test project I don't have any problems, objects are created and I use the NSManageObjectContextDidSaveNotification to communicate the changes to the main thread.

I believe my problem lies in my ignorance of GCD. I am parsing XML and in parserDidEndDocument: I need to save data to CoreData without blocking the the UI. Whenever this block is used my apps memory starts to snowball uncontrollibly until finally I get Terminated app due to memory pressure.

Notes: I use AppDelegate's singleton to hold my NSPersistentStoreCoordinator and stuffToSave is an NSMutablearray created by my parser.

Any direction would be greately appreciated. I've been beating my head for 2 days!

-(void)parserDidEndDocument:(NSXMLParser *)parser

dispatch_queue_t backgroundQ = dispatch_queue_create("com.example.myapp", NULL);

__block AppDelegate *app= [[UIApplication sharedApplication]delegate];
__block NSMutableArray *array = self.stuffToSave;


dispatch_async(backgroundQ, ^(void){

    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
    context.persistentStoreCoordinator = [app persistentStoreCoordinator];

    HNField *field = [HNField fieldWithField_id:[NSNumber numberWithInt:0] inContext:context];
    //initalize array if needed
    if (!field.arrayOfPolylines) field.arrayOfPolylines = [[NSMutableArray alloc]init];

    //add polyline to array to save in database
    for (id obj in array) {
        if ([obj isKindOfClass:[HNPolyline class]]) {
            HNPolyline *theLine = (HNPolyline *)obj;
            [field.arrayOfPolylines addObject:theLine];
        }else if ([obj isKindOfClass:[HNParserPoint class]]){
            HNPoint *point = [HNPoint createAPointWithContext:context];
            HNParserPoint *pPoint = (HNParserPoint *)obj;
            point.point_id = pPoint.point_id;
            point.lat = pPoint.lat;
            point.lng = pPoint.lng;
            point.yield = pPoint.yield;
            point.farm_id = self.farm_id;
            point.field_id = self.field_id;
            point.inField = field;
            //add every point in database
            [field addFieldPointsObject:point];
        }
    }
    NSError *error;
    [context save:&error];

       });



self.stuffToSave = nil;
self.parser = nil;
}

Edit 1: I am listening for NSManageObjectContextDidSaveNotification from a different class than where I am doing the parsing. In the viewDidLoad I have:

// observe the ParseOperation's save operation with its managed object context
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(didSave:)
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:nil];

Then I am using the below from Apple's "ThreadedCoreData" example.

-(void)didSave:(NSNotification *)notification{

    if (notification.object != [self.app managedObjectContext]) {
        NSLog(@"not main context");
        [self performSelectorOnMainThread:@selector(updateMainContext:) withObject:notification waitUntilDone:NO];
    }else{
         NSLog(@"b Thread: %@",[NSThread currentThread]);
       NSLog(@"main context");
    }
}

// merge changes to main context
- (void)updateMainContext:(NSNotification *)notification {

    assert([NSThread isMainThread]);
    [[self.app managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
    NSLog(@"did save");
}
È stato utile?

Soluzione

Maybe I am not able to understand properly, but from what I am seeing, there are few things I would avoid.

I maybe wrong, but I think the problem is that you are launching an async operation on a background queue, but you end up working with the default context queue which you guarantee to be thread safe but it is not. So when you are saving you send notification, which is triggering a save and so on, until you fill up memory. To verify this, try to put a break point in updateMainContext, and see how many times it is invoked.

If you are working with different contexts you should initialize them with an NSPrivateQueueConcurrencyType, this way you are sure that all the work is done in a separate thread. So I would revisit like this in place of dispatch async, in this case you can do this in place of dispatch_async:

NSManagedObjectContext *privateCtx = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 

[privateCtx performBlock:^() {
  // put the code you have in dispatch async...
}];

Then on the other class you should find a way to listen for the private context only, of course you need the reference. If you can't I suppose it is fine to avoid anything that come from the main thread, if that class is only meant to be a point of merge from the background.

Altri suggerimenti

I didn't mention or realize when I posted that I am using Google Maps SDK (1.6.1.6332) and that the google maps sdk uses core data as well. I was observing NSManagedObjectContextDidSaveNotification in my app delegate but failed to filter notifications. Therefore google maps was sending out the NSManagedObjectContextDidSaveNotification and my app was attempting to merge those changes, which was the source of my problem.

I don't know if this is the best solution but works for my purposes. Since I am testing the notification in my app delegate I only have a reference to my main MOC so I use it to make sure the notification didn't come from it, then I test if the notification came from the class of interest by testing the string description of the notification (which contains multiple instances of the class of interest that needs to be merged). Doing so blocks google maps from attempting to merge its changes with my model. Props to @Leonardo for his input which ultimately lead to the answer.

- (void)mergeChanges:(NSNotification *)notification {

    NSLog(@"saved changes called");
    if (notification.object != self.managedObjectContext) {
        NSLog(@"call was not main MOC");
        NSString *testingString = notification.description;
        if ([testingString rangeOfString:@"HNDemoResponse"].location != NSNotFound) {
            [self performSelectorOnMainThread:@selector(updateMainContext:) withObject:notification waitUntilDone:YES];
        }else NSLog(@"call was goole maps");
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top