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 NSManagedObject
s 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");
}
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:
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.