Reputation: 1301
While trying to learn more about Core Data and multithreading, I wrote an app that fails to save to core data. First some background. Before my view appears, I create managed context. This happens in the main thread:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.managedObjectContext) [self useDocument];
}
- (void)useDocument
{
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:MY_DOCUMENT];
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
[document saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
NSLog(@"@@@AMRO--->managedObjectContext has been setup");
self.managedObjectContext = document.managedObjectContext;
[self refresh];
}
}];
} else if (document.documentState == UIDocumentStateClosed) {
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
NSLog(@"@@@AMRO--->managedObjectContext has been opened");
self.managedObjectContext = document.managedObjectContext;
[self refresh];
}
}];
} else {
NSLog(@"@@@AMRO--->managedObjectContext has been set");
self.managedObjectContext = document.managedObjectContext;
}
}
When my view appears, I fetch data from a client using this approach. The reason I do this is because I do not want my view to be blocked as my client refresh is happening:
dispatch_queue_t fetchQ = dispatch_queue_create("Client Fetch", NULL);
dispatch_async(fetchQ, ^{
[self.managedObjectContext performBlock:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self reload];
dispatch_async(fetchQ, ^{
[self refreshClientInformation];
});
});
}];
});
The call to refreshClientInformation
, takes about 2 minutes to retrieve
information, however, I want the view to load in the meantime with whatever I
have. The above approach prevents me
from being able to save to context despite the explicit call to
[self.managedContext save:&error]
in refreshClientInformation
after each client data update. refreshPersonalClientInformations
executes in the "Client Fetch" thread queue.
- (void) refreshPersonalClientInformation
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"CLIENT"];
request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"stationId" ascending:YES]];
NSDate *twoHoursAgo = [NSDate dateWithTimeIntervalSinceNow:-2*60*60];
request.predicate = [NSPredicate predicateWithFormat:@"conditionsUpdated < %@", twoHoursAgo];
NSError *error = nil;
int counter = 0;
// Execute the fetch
NSArray *matches = [self.managedObjectContext executeFetchRequest:request error:&error];
// Check what happened in the fetch
if (!matches) { // nil means fetch failed;
NSLog(@"ERROR: Fetch failed to find CLIENT %@ in database", DEFAULT);
} else if (![matches count]) { // none found, so lets retrieve it below
return;
} else {
for (CLIENT *client in matches) {
NSDictionary *clientDict = [ClientFetcher clientInfo:client.clientId];
client.name = [clientDict[NAME] description];
client.age = [clientDict[AGE] description];
client.updated = [NSDate date];
if (![self.managedObjectContext save:&error]) {
NSLog(@"@@@@@@@@@@@@@@@@@@@@@@@@@@AMRO Unresolved error %@, %@", error, [error userInfo]);
}
//Have to rate limit requests at no more than 10 per minute.
NSLog(@"Sleeping for 6s");
sleep(6);
NSLog(@"Done sleeping for 6s");
}
}
}
I even tried to run the save in refreshPersonalClientInfo by doing this:
dispatch_async(dispatch_get_main_queue(), ^{
[self.managedObjectContext save:nil];
});
But that did not work, no errors were detected
As an experiment I changed the thread call to this when my view appears and my
core data eventually gets saved, the problem is that my view is blocked and I have to
wait a long time while the data is getting retrieved. Also when I say eventually, I mean that it does not happen after I call [self.managedContext save:nil]
, but after a few minutes:
dispatch_queue_t fetchQ = dispatch_queue_create("Client Fetch", NULL);
dispatch_async(fetchQ, ^{
[self.managedObjectContext performBlock:^{
[self refreshPersonalClientInformation];
dispatch_async(dispatch_get_main_queue(), ^{
[self reload];
});
}];
});
Any suggestions on how I should retrieve my informaion in a background thread and be able to save the data to disk while I still leave my UI accessible?
Upvotes: 0
Views: 1004
Reputation:
You're better to create a child context to perform everything in the background:
dispatch_queue_t fetchQ = dispatch_queue_create("Client Fetch", NULL);
dispatch_async(fetchQ, ^{
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.parentContext = self.managedObjectContext;
[context performBlock:^{
[self refreshPersonalClientInformation];
dispatch_async(dispatch_get_main_queue(), ^{
[self reload];
});
}];
});
Upvotes: 1