What happens when NSManagedObjectContext has both persistent store and parent context set and save is called? Will it push the data both to persistent store and the parent context one by one? Or would it do it concurrently? Or would core data simply throw a complaining exception?
API does not directly stop one from setting two "parents" for a given context.
This will happen:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Context already has a coordinator; cannot replace.'
This happens because when you set parentContext, the persistentStoreCoordinator is automatically set to the persistentStoreCoordinator of the parent context.
We do not need to set the coordinator for managed context if we assign the coordinator for the parent context
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[self setManagedObjectContext:moc];
[self setPrivateContext:[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]];
/// when you set parentContext, the persistentStoreCoordinator is automatically set to the persistentStoreCoordinator of the parent contex
[self.privateContext setPersistentStoreCoordinator:coordinator];
[self.managedObjectContext setParentContext:self.privateContext];
Here is the complete code for use in Framework example -
NSURL *modelURL = [[NSBundle bundleForClass:[self class]] URLForResource:#"TVModelSDK" withExtension:#"momd"];
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSAssert(mom, #"Failed to initialize mom from URL: %#", modelURL);
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
/// DONOT set coordinator for managed Context !
// [moc setPersistentStoreCoordinator:coordinator];
[self setManagedObjectContext:moc];
[self setPrivateContext:[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]];
/// when you set parentContext, the persistentStoreCoordinator is automatically set to the persistentStoreCoordinator of the parent contex
[self.privateContext setPersistentStoreCoordinator:coordinator];
[self.managedObjectContext setParentContext:self.privateContext];
Related
I am trying to use PFIncrementalStore, http://sbonami.github.io/PFIncrementalStore/
After setting up as instruction, I get the following error at [context performBlock:^,
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Can only use -performBlock: on an NSManagedObjectContext that was created with a queue.'
Some internet search said, NSManagedObjectContext must be created with NSPrivateQueueConcurrencyType.
If I look up all "NSManagedObjectContext init" in PFIncrementalStore.m, I get two occurrence of
_backingManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
Why am I getting that error? How should I edit PFIncrementalStore?
Thanks.
I solved it by replacing
_managedObjectContext = [[NSManagedObjectContext alloc] init];
with
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
I have a basic question regarding Core Data.
I have 2 tables one to many.
I have setup the app to add children to the parent, but I cannot understand how I set the relationship so that when I add a new child via a view controller that It adds the child to the correct parent.
I have generated the entity subclasses and have managed to get the app to add a child (but it adds it to index 0), but I cant seem to work a fetchrequest that finds the correct parent.
- (IBAction)save:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
Child *newChild = [NSEntityDescription insertNewObjectForEntityForName:#"Child" inManagedObjectContext:context];
[newChild setValue:self.childName.text forKey:#"childName"];
[newChild setValue:self.born.text forKey:#"born"];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ParentList" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
ParentList *parent = [fetchedObjects objectAtIndex:0]; //this adds it to the first parentList in list at index 0 not to the correct parent
NSLog(#"parent: %# created", league);
[parent addChildObject: newChild];
//
///////////////////////////////////////////
//////index path is wrong//////////////////
///////////////////////////////////////////
}
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
You need to pass the parent's objectID to the second view controller (if I understood your setup correctly).
Fetch the parent (using existingObjectWithID:error: of the NSManagedObjectContext) in the other view context.
Set the child parent as the fetched object.
should look something like:
NSError* error = nil;
NSManagedObjectID* parentID = //the parent object id you selected
Parent* parent = [context existingObjectWithID:parentID error:&error];
if (parent) { //parent exists
Child *newChild = [NSEntityDescription insertNewObjectForEntityForName:#"Child"
inManagedObjectContext:context];
[newChild setValue:self.childName.text forKey:#"childName"];
[newChild setValue:self.born.text forKey:#"born"];
[newChild setValue:parent forKey:#"parent"];//Set the parent
} else {
NSLog(#"ERROR:: error fetching parent: %#",error);
}
Edit:
To get the selected object id (assuming you are using a NSFetchedReaultsController):
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
//Use object.objectID as the selected object id to pass to the other view controller
//what ever you need to do with the object
}
With Core Data, I am able to save and fetch objects in memory; however, when I restart my app (in the simulator) and fetch the results, the object is fetched but all the attributes are nil. I have included all protocol methods in my appdelegate and having initialized my managedObjectContext as such:
managedObjectContext = [self managedObjectContext];
in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
Here is what I am getting in the logs...
Correct:
Fetching results log (app not restarted) --> Name: new entry!!!!
Incorrect:
Fetching results log (app restarted) --> Name: (null)
-But, object still exists in persistantStore
--
//Save entry
Entry *entry = (Entry *)[NSEntityDescription insertNewObjectForEntityForName:#"Entries" inManagedObjectContext:managedObjectContext];
[entry setTitle:#"new entry!!!!"];
NSError *error;
if(![managedObjectContext save:&error]){
NSLog(#"SAVE ERROR!!!");
}
//Fetch entry
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Entries" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *info in fetchedObjects) {
NSLog(#"Name: %#", [info valueForKey:#"title"]);
}
Are you sure your Entry class realizes the title property using #dynamic and not #synthetize?
I have a core-data app that runs without crashing if I perform a fetch inside viewDidLoad like this:
- (void) performCoreDataFetch {
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
exit(-1); // Fail
}
}
- (void)viewDidLoad {
[super viewDidLoad];
[self performCoreDataFetch];
}
The only problem with the above way of performing fetch is if the data to be returned is big, it freezes the app for a few seconds (but does return correct result without crashing every single time), so to avoid that I decided to use dispatch_async (code shown below) and call [self performCoreDataFetch] inside it.
But if I run this same [self performCoreDataFetch] inside dispatch_sync within viewDidLoad, like shown below,:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performCoreDataFetch];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
Calling [self performCoreDataFetch] within dispatch_async crashes the app randomly saying "-[NSFetchRequest fetchLimit]: message sent to deallocated instance"
My fetchedResultsController method looks like this:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
// Create and configure a fetch request with the Organization entity
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"Organization" inManagedObjectContext:managedObjectContext];
request.fetchBatchSize = 20;
// create sortDescriptor array
NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray *sortDescriptorArray = [NSArray arrayWithObjects:nameDescriptor, nil];
request.sortDescriptors = sortDescriptorArray;
NSPredicate *predicate = nil;
predicate = [NSPredicate predicateWithFormat:#"state LIKE %#", filterByState];
[request setPredicate:predicate];
// Create and initialize the fetchedResultsController
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc ] initWithFetchRequest:request managedObjectContext:managedObjectContext sectionNameKeyPath:#"uppercaseFirstLetterOfName" cacheName:nil];
self.fetchedResultsController = aFetchedResultsController;
fetchedResultsController.delegate = self;
// Memory management
filterByState = nil;
// [sortDescriptorArray release];
[nameDescriptor release];
// [predicate release];
[request release];
[aFetchedResultsController release];
return fetchedResultsController;
}
Core data is not thread safe if you perform a fetch for a fetchedResultsController. It makes sense, as the fetchedResultsController is the datasource of your UI. Rather than perform a fetch, set your fetchedResultsController to nil and reload your tableView.
Core data is not thread save. To be more exact, the NSManagedObjectContext is not save. All NSManagedObject belong to a particular NSManagedObjectContext and they are not interchangeable.
Pre IOS 5 you need to put really really complicated method. Basically each thread require it's own NSManagedContext
After IOS5, you can do:
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
Then you can do
[__managedObjectContext performBlock ^{
//Some really long operation
}]
on any thread that is not main thread.
That will do it on a different thread however in a thread save way. Basically core data will put your operation into queues and it will execute that one by one locking the managedObjectContext for each operation.
I am using multithreading while loading data from the database.
I am doing the following
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
// Get data
NSDate *pastDate = [CommonHelper getSelectedDateYearBackComponents:[NSDate date]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"users == %# && startDate >= %#", objUser,pastDate];
NSMutableArray *fetchArray = [DataAccess searchObjectsInUserContext:#"userIngo" :predicate :#"startDate" :NO];
if ([fetchArray count] > 0)
{
dispatch_async(dispatch_get_main_queue(),
^{
// Reload table
[self.tableView reloadData]; });
}
else
{
dispatch_async(dispatch_get_main_queue(),
^{ // calling Webservice
});
}
});
where users is the entity from which I am trying to fetch data and objUser is the user object for whom I am retrieving data from the users entity
and my searchObjectsInUserContext code is like this
+(NSMutableArray *) searchObjectsInLabContext: (NSString*) entityName : (NSPredicate *) predicate : (NSString*) sortKey : (BOOL) sortAscending
{
i3EAppDelegate *appDelegate = (i3EAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setUndoManager:nil];
[context setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
// Register context with the notification center
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
[request setEntity:entity];
[request setFetchBatchSize:10];
[request setReturnsObjectsAsFaults:NO];
// If a predicate was passed, pass it to the query
if(predicate != nil)
{
[request setPredicate:predicate];
}
// If a sort key was passed, use it for sorting.
if(sortKey != nil)
{
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:sortAscending];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
}
NSError *error;
NSMutableArray *mutableFetchResults = [[context executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
// NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[request release];
appDelegate = nil;
return [mutableFetchResults autorelease];
}
So in my searchObjectInUserContext I use multiple managedObjectContext so that it does not create problems for me while multithreading as explained by Fred McCann in his blog post.
However, my problem is at NSMutableArray *mutableFetchResults in my searchObjectsInUserContext because it returns 0 at times even though there is data in the database.
Can someone help me with what I am doing wrong?
You are leaking the context you create; you never release it.
There is no need to register as an observer because you are never saving with that context.
Why are you making a mutable copy of the context? That rarely serves any useful purpose.
How do you know there is data in the database?
Is this method being run on a background thread?
Update
There is nothing wrong with your fetch so I suspect the issue might be one of timing. Is this fetch being run before another thread saves? That would explain the behavior you are seeing.
Also, why are you running this on a background thread? Are you seeing a performance issue that requires a background search like this?
Update 2
First, I still question the need for the background fetching. That is normally reserved for when you have performance issues as fetching is very fast.
Second, you fetch on a background queue but then you don't do anything with the data you fetched. You do not hand it off to the table (which would be bad anyway) you just fetch and throw it away.
If you are fetching just to count then don't fetch, do a -[NSManagedObjectContext -countForFetchRequest: error:]. It will be even faster and removes the need for the background queue call.
If you are expecting to do something with the results you have an issue. Instances of NSManagedObject cannot cross a thread/queue boundary. So fetching on the background queue does very little for the UI. You need to fetch those objects on the main queue/main thread. If you don't you will crash because Core Data does not allow it.
In the end, your background queues are doing you more harm than good and they are not solving the problem you are trying to solve.