I have a core data with NSBinaryStoreType persistent store which I init its managedObjectContext with NSPrivateQueueConcurrencyType.
I have a NSFetchedResultsController property in my singleton class which I init like that:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([CachedURLResponse class])];
fetchRequest.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"messageId" ascending:YES]];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
[self.fetchedResultsController performFetch:&error];
i.e , This objectContext has only entity of one type.
When I insert or delete objects my fetchObjects stays the same.
For instance if I print
self.fetchedResultsController.fetchedObjects;
before and after the following line:
MyEntity *myEntity = [NSEntityDescription insertNewObjectForEntityForName:#"MyEntity" inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
The result remains the same.
Do I need to performFetch after every time I delete or insert an object?
Is there any way to auto-fetch them?
I know about the listen to the notification solution but not sure about what event I should listen to. Please let me know if this is the correct behaviour or if you have efficient solution
Related
I use Core Data to store shops as Entity Shop and on every shop I have an Attribute city, I want to group the shops by city and present a UITableView with all the cities. I use NSFetchedResultsController to fetch the data and refresh the UITableView and since I want to group the cities I set the resultType of the request as NSDictionaryResultType.
However now when I use objectAtIndexPath at my NSFetchedResultsController I am getting NSKnownkeysdictionary1 My question is can I make the NSFetchedResultsController handle the NSDictionaryResultTypeor I should drop the use of NSFetchedResultsController in this case and take some other approach?
You have to set the NSExpressionDescription for it to fetch the appropriate value. You could do it like this,
- (NSFetchedResultsController *)fetchedResultsController
{
if (!_fetchedResultsController) {
NSExpression *cityKeypath = [NSExpression expressionForKeyPath:#"identifier"];
NSExpressionDescription *cityDescription = [[NSExpressionDescription alloc] init];
cityDescription.expression = cityKeypath;
cityDescription.name = #"City";
cityDescription.expressionResultType = NSStringAttributeType;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Shop"];
[fetchRequest setPropertiesToFetch:#[cityDescription]];
[fetchRequest setPropertiesToGroupBy:#[#"city"]];
[fetchRequest setResultType:NSDictionaryResultType];
fetchRequest.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"city" ascending:YES]];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
_fetchedResultsController.delegate = self;
NSError *error;
[_fetchedResultsController performFetch:&error];
}
return _fetchedResultsController;
}
So, you need to provide NSExpressionDescription for the properties you want to fetch. The NSFetchedResultsController result will in in dictionary type like this,
{
#"city": "Kansas"
}
...
How can I change my code so it could run on different queue then the main queue:
- (NSFetchedResultsController *)fetchResultController{
if (_fetchResultController != nil) {
return _fetchResultController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Recipes"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"headline"
ascending:YES];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(category = %#)",_categoryToShown];
[fetchRequest setPredicate: predicate];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
_fetchResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
_fetchResultController.delegate = self;
return _fetchResultController;
}
A NSFetchedResultsController works with UITableViews or UICollectionViews. These are part of the UIKit and so they should be touched only on the main queue.
In addition NSFetchedResultsController has been developed to get data through lazy loading features (data batches) in order to reduce the memory footprint.
So, what's your goal? Why do you need to change the queue you are running on?
Edit 1
when i click a button in my UI its segue to table view that fill the
data according to this fetch request.i want that when i click the
button its automatically segue to the table view so i need to write
this code with multithreading
If you want to achieve what you wrote in the comment you should create a fetch request and execute it in a background thread..In this way when you'll land on the segue results will be available on internal cache of the persistent store coordinator. This technique is called as "warming up the cache" by Marcus Zarra (CoreData weird behavior when data are loaded on background thread). With the following approach the usage of NSFetchedResultsController is not useful. So, before taking this approach why don't you try to play a bit with
[fetchRequest setFetchBatchSize:20];
If the table (or collection) will display 10 elements, 20 as batch size it's ok. Anyway Instruments is your friend.
In addition, I really suggest to have a look to Core Data performance is a balance.
I'm trying to use NSFetchedResultsController to display data in a table view. My data model has an array of users, each user has an array of categories, each category has an array of organizations, and each organization has an array of accounts. The table view displays data from a single user. Each row represents an organization belonging to the user, and the organizations are separated into sections, with each section containing the organizations belonging to a specific category. In my app delegate I populate my application with some dummy data, and NSFetchedResultsController displays that data fine. The problem is that when I try to add a new organization to an existing category the row is added but it is added to a new section containing only that row, as opposed to merging with the section that holds the rest of the organizations for that category.
Here is the code for my NSFetchedResults controller:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"PKOrganization" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Tell the fetch request to only retrieve the organizations from the proper user
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(category.user.name = '%#')", self.user.name]]];
// Sort the data
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"category.name" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor1,sortDescriptor2];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"category" cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
It might also be worth noting that I am able to tell that the data is added as expected. It just doesn't get displayed correctly.
Try changing this line
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"category" cacheName:#"Master"];
To this:
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"category.name" cacheName:#"Master"];
In my experience, I needed the sectionNameKeyPath to be exactly the same as the first sortDescriptor in order to get sections to work properly.
Suppose you have two entities, one is People, the other is Location. Location has an attribute name, People has a to-one relationship to Location named location.
Then if you have an NSFetchedResultsController like below, you can't get the delegate callback when you change the Locations name.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"People"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"location.name"
ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"location.name"
cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
I know I can fetch the Location entity and then filter it by name to solve this problem. But I'm curious about this, does the NSFetchedResultsController delegate just notify you when something changed in the entity's relationship,not the relationship's attribute.
Can anyone give me some posts about this feature. I search the web and Apple's documentation but can't find a reasonable description.
does the NSFetchedResultsController delegate just notify you when something changed in the entity's relationship,not the relationship's attribute.
Exactly. Because a change in the relationship is a change in the object that the FRC is observing. But a change in the properties of one of the objects at the other end of the relationship will not be notified to the delegate because those objects are not being observed by the FRC.
I have this working code, but now i need to be able to change the NSPredicate, based on the object used in the predicateWithFormat as follows:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Item" inManagedObjectContext:_context];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"self.atrObj == %#", _currentUser.atrObj.objectID]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortContent = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortContent]];
[fetchRequest setFetchBatchSize:10];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:_context sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
self.fetchedResultsController.delegate = self;
I need the fetchedResultsControlelr to use the new _currentUser object when i change the _currentUser object from the app delegate which has this tableviewcontroller a property.
[self.tableViewcontroller setCurrentUser:user];
Thank you!
You can change the predicate anytime but be sure to re-fetch via performFetch. Also from Apple docs:
Important: Important If you are using a cache, you must call deleteCacheWithName: before changing any of the fetch request, its
predicate, or its sort descriptors. You must not reuse the same
fetched results controller for multiple queries unless you set the
cacheName to nil.
Also, from the Apple docs of NSFetchedResultsController:
Modifying the Fetch Request You cannot simply change the fetch request
to modify the results. If you want to change the fetch request, you
must:
If you are using a cache, delete it (using deleteCacheWithName:).
Typically you should not use a cache if you are changing the fetch
request.
Change the fetch request.
Invoke performFetch:.
Whenever the value of currentUser changes, do this (perhaps inside the setter):
NSError *error = nil;
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:#"atrObj == %#", self.currentUser.atrObj];
if(![self.fetchedResultsController performFetch:&error]) {
// Handle errors
}