Quite simply I want to iterate through CoreData's [self.fetchedResultsController fetchedObjects] (More specifically I am using MagicalRecord in this example) and change all items that match my criteria (the if statement), then make changes to those objects.
for (Task *aTask in [[self.fetchedResultsController fetchedObjects] mutableCopy]) {
if ([aTask.day past] && [[aTask isArchived] isEqual:#(NO)]) {
NSManagedObjectContext *context = [NSManagedObjectContext MR_defaultContext];
[context MR_saveOnlySelfWithCompletion:^(BOOL success, NSError *error) {
aTask.day = date;
}];
}
}
But as it turns out it doesn't work! I am trying a solution where I don't have to manually manage everything CoreData as this answer specifies and instead find a succinct solution to the problem, few lines of code and not much iteration.
EDIT: Following from Dan's answer, I have edited my imperfect code to...
NSPredicate *uncompletedTasks = [NSCompoundPredicate andPredicateWithSubpredicates:#[[NSPredicate predicateWithFormat:#"day < %# AND isArchived = %#",[NSDate date],#NO]]];
self.fetchedResultsController = [Task MR_fetchAllSortedBy:#"dateScheduled" ascending:YES withPredicate:uncompletedTasks groupBy:nil delegate:self inContext:[NSManagedObjectContext MR_defaultContext]];
for (Task *aTask in [self.fetchedResultsController fetchedObjects]) {
aTask.day = date;
}
NSManagedObjectContext *context = [NSManagedObjectContext MR_defaultContext];
[context MR_saveOnlySelfWithCompletion:^(BOOL success, NSError *error) {}];
This solves the problem, although only momentarily when I update the view, I get this error:
CoreData: error: Serious application error. Exception was caught
during Core Data change processing. This is usually a bug within an
observer of NSManagedObjectContextObjectsDidChangeNotification. ***
-[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects1 with userInfo (null)
I really know nothing about MagicalRecord, but ...
If you iterate through all items fetched by the FRC just to make an update to some of them you better:
1) perform the update in the background
2) fetch only the objects you need updated. example predicate:
NSPredicate* needUpdate = [NSPredicate predicateWithFormat:#"day < %# AND isArchived = %#",[NSDate date],#NO];
NSPredicate* p = [NSCompoundPredicate andPredicateWithSubpredicates:#[FRC_predicate,needUpdate]];
3. perform a single save after you updated all objects (or in batches, don't save one by one)
Guessing ...
You wrote code that make an update to an object (aTask.day = date;) in a completion block.
This might not actually persist the change the way you think it does.
make the update before you call the "save" procedure.
Several problems here.
First, I am not sure why you need mutableCopy. You are not modifying the members of the array yo are iterating through, so this should not be necessary. I am not even sure what the effect of making a managed object copy is in this case. Just leave it out.
Second, your boolean comparison is a bit confusing and can be written in a simpler way. This should be enough:
"... && aTalk.isArchived.boolValue"
Third, you are changing the object in the completion block after the save. So you are not saving the change, just perhaps the previous one made in some earlier iteration through the loop. Instead, just write the change and call save after the loop.
When saving, make sure the used context is the same as that of the fetched results controller.
Related
There are a lot of questions on this topic but perhaps all too specific and none too concise.
I have a NSFetchedResultsController. It initially fetches the data I need. When I update the data model which would affect the results of the NSPredicate of the NSFetchRequest, the content does not update.
More concretely, I have a Permissions model. There are data objects that are assigned permissions, then there are users who have a subset of these permissions, though the data object's Permission is not the same as the User's permission; they do share the same controlKey.
So, the NSPredicate is:
[NSPredicate predicateWithFormat:#"controlKey IN %#", [[User currentUser].permissions valueForKey:#"controlKey"]];
The problem is, if I create and add a permission, the content does not update when I save the NSManagedObjectContext (yes, I'm observing it. No, I'm not using a cache so nothing to delete).
I'm pretty convinced it's because of the predicate. It's still the same array as initially, and doesn't get updated.
My question is, how do I write a predicate that gets me what I want but still remains "dynamic" ? It would be nice to have UITableView animations as this object is added.
Here is what you need to do. When detecting the change,
self.fetchedResultsController.predicate = //new predicate
[self.fetchedResultsController performFetch:nil];
[self.tableView reloadData];
Or sometimes it is necessary to wipe the FRC clean.
_predicate = // new predicate, put it into an ivar
self.fetchedResultsController = nil;
[self.tableView reloadData]; // lazily re-instantiate FRC with _predicate
This could be expensive, but in order to get animations, you could try replacing reloadData with the following:
[self.tableView beginUpdates];
[self.tableView endUpdates];
You query the control keys from a set of permissions and pass them to the predicate. The predicate itself isn't dynamic anyway, it is only the result of the fetch which is dynamic. So, you can't do exactly what you want.
Instead, you should be observing the permissions change for the user, creating a new predicate, updating the FRC and re-executing it (a requirement after you change the predicate, because it is expected to be static).
I have tried with following code snippet and it works for me:
[NSFetchedResultsController deleteCacheWithName:nil];
[self.fetchedResultsController.fetchRequest setPredicate:predicate]; // set your new predicate
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"%#, %#", error, [error userInfo]);
abort();
}
I'm trying to best format my project's use of RestKit and Core Data. There are a couple of things that I've got working, but I have a feeling they are implemented poorly and potentially thread unsafe... I have an object that manages all of my data transfer, storage, etc., which has a function that sets up restkit. I have an instance variable that I use for RKObjectManager, and in this setup function I create the objectStore, setup all the attribute mappings, create the persistent store, etc., - all of the normal restkit setup code. Outside of this function, the only thing available to this object is the _objectManager instance variable, which I've been using for NSFetchRequests and such.
There are two things I want to make sure I'm implementing properly, fetching managed objects, and savings changes to managed objects.
If I want to update a property on an object, I've been doing this:
object.whatever = #"something here";
NSError *error;
if (![object.managedObjectContext save:&error]) {
// log the error here
}
Is this the proper way to update/save a property on an object? Is accessing the object's managed object context directly save to do at any point in the code, or is this something that should be done only in the background/foreground? My current implementation could potentially have this called in both the background and foreground and I just want to make sure this is acceptable.
When I want to fetch an object, I wrote a function that takes an entity name, an array of predicates, and a sort descriptor as parameters so it can be reused:
NSManagedObjectContext *managedObjectContext = // I DONT KNOW WHAT TO PUT HERE! //
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
[fetchRequest setPredicate:compoundPredicate];
NSError *error;
NSArray *fetchedRecords = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error) {
// log error
}
// if we were given a sort descriptor, sort the array appropriately
if (sortDescriptor) {
fetchedRecords = [fetchedRecords sortedArrayUsingDescriptors:#[sortDescriptor]];
}
return fetchedRecords;
My problem here is creating/accessing the correct managed object context. How should I do this? Do I access some property on the RKObjectManager I created before such as:
_objectManager.managedObjectStore.mainQueueManagedObjectContext
or is that not thread safe because its for the main thread? What can I do to make sure I'm using the right managed object context, and that it is thread safe? I was using:
_objectManager.managedObjectStore.persistentStoreManagedObjectContext
but I was told this was definitely not best practice and was not thread safe, so I'm trying to determine the best solution.
EDIT - perhaps I can call this function to get the context whenever I want to fetch objects?
- (NSManagedObjectContext *)getManagedObjectContext {
if ([NSThread isMainThread]) {
return _objectManager.managedObjectStore.mainQueueManagedObjectContext;
}
else {
return [_objectManager.managedObjectStore newChildManagedObjectContextWithConcurrencyType:NSPrivateQueueConcurrencyType tracksChanges:YES];
}
}
For saving, instead of this:
if (![object.managedObjectContext save:&error]) {
you should do:
if (![object.managedObjectContext saveToPersistentStore:&error]) {
so that the changes are persisted right up the chain to the on-disk store. You should only be doing this on the thread that created / fetched the managed object (thus the thread ownership of the MOC is maintained).
Foreground / background isn't important so much as which MOC is used by each thread. If the MOC thread ownership is respected then you should be fine.
Same applies to fetching. For UI updates, you must use the main thread and the mainQueueManagedObjectContext. You should never directly use the persistentStoreManagedObjectContext. For arbitrary background threads you should be asking the managed object store to create a new child managed object context for you and using that.
I'm still coding my RSS reader and I have gotten to the point where I'd like things to go smoother by background filling my Feeds at once with the newest Posts.
The problem is that it crashes my app quite badly with messages such as:
2013-10-02 21:06:25.474 uRSS[97209:a0b] *** Terminating app due to uncaught
exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource
must return a cell from tableView:cellForRowAtIndexPath:'
(stacktrace)
I came to the conclusion that I am not running thread safe here and I then discovered this kind of CoreData snippets:
//Core Data's NSPrivateQueueConcurrencyType and sharing objects between threads
[context performBlock:^{
// fetch request code
NSArray *results = [context executeFetchRequest:request error:nil];
dispatch_async(dispatch_get_main_queue(), ^(void) {
Class *firstObject = [results objectAtIndex:0];
// do something with firstObject
});
}];
// Assume we have these two context (They need to be set up. Assume they are.)
NSManagedObjectContext *mainMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType] autorelease];
NSManagedObjectContext *backgroundMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
// Now this can safely be called from ANY thread:
[backgroundMOC performBlock:^{
NSArray *results = [context executeFetchRequest:request error:nil];
for (NSManagedObject *mo in results) {
NSManagedObjectID *moid = [mo objectID];
[mainMOC performBlock:^{
NSManagedObject *mainMO = [mainMOC objectWithID:moid];
// Do stuff with 'mainMO'. Be careful NOT to use 'mo'.
}];
}
}];
Now, what I would like to know is the following:
should the backgroundMOC be defined as a Class member property, or everytime the method that uses it is invoked?
what if this method is itself invoked asynchronously (the RSS parsing method create the objects on the fly)?
How may I securely notify my UITAbleView that my MOC's been updated so that it can refresh without crashing?
Does this only apply to fetches, or also to objects insertions, deletions, etc?
Where could I find a working example of this concept successfully applied?
1) backgroundMOC should be defined in the scope, where you use it. Say, if you use context inside of SomeClass, it's good to define it as property of SomeClass. However, usually many classes share same context (for example, it's quite OK to share mainMOC between all your viewControllers) so i suggest to define mainMOC and backgroundMOC in your AppDelegate or some other singleton.
2) It's OK. However, it's bad idea to create contexts every time — see 1 and initialize them once in singleton.
3) Take a look at NSFetchedResultsController. It's exactly what you need to setup your tableView and track CoreData changes.
4) Yes
5) Cannot really point you to working example. Find something out on developer.apple.com =)
Also remarks:
1) Your class cannot be named Class
2) Use existingObjectWithID:error:, not objectWithID: — check this answer, it was really annoying issue in my experience
3) Read about NSManagedObjectContext concurrency patterns
I have a CoreData entity Tracker which stores the dates.
The app receives a notification and the CheckListViewController enters data in CoreData for up to 13 days, so when the CheckListViewController gets dismissed, the CoreData entity Tracker will be filled with 13 rows.
In the MainViewController (which dismisses CheckListViewController), I have the following code:
- (void)dataSaved {
self.checkListVC dismissViewControllerAnimated:YES completion:^{
// fetching all the data from 'Tracker' entity and doing NSLog on it
// all data gets logged in console without any issues
}];
}
Now, after that somewhere in my code, I fetch all the data from the entity Tracker but the return data is empty. The CoreData doesn't show any error it simply returns and empty array.
Edit:
Code to fetch results from CoreData
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:ENTITY];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
[request setSortDescriptors:#[sortDescriptor]];
request.predicate = (fromDate && toDate) ? [NSPredicate predicateWithFormat:#"date >= %# AND date <= %#", fromDate, toDate] : nil;
__block NSArray* fetchedHabits;
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
fetchedHabits = [managedObjectContext executeFetchRequest:request error:&error];
if (error) NSLog(#"Unknown error occurred while fetching results from CoreData, %# : %#", error, [error userInfo]);
}];
CoreData model:
Update 1:
So as you can see there are two entities, namely Habit and Tracker. When I fetch results from Habit it all works fine, but when I try to fetch results from Tracker it gives me an empty array. I have a common NSManagedObjectContext instance because you can manage multiple CoreData entities with single managedObjectContext.
I have checked managedObjectContext.persistentStoreCoordinator.managedObjectModel.entitiesByName and it also lists both the entities.
Update 2:
Code where I add data in to Tracker
TrackerCoreData *tracker = [NSEntityDescription insertNewObjectForEntityForName:ENTITY
inManagedObjectContext:managedObjectContext];
tracker.date = date;
tracker.habits = habits;
// saving CoreData explicitly
NSError *error = nil;
[managedObjectContext save:&error];
There could be many reasons for your failure to display the records:
data was not saved
data was not retrieved correctly
data was not displayed correctly
All of these could be potentially complicated scenarios, but you should check them in this order.
A much better approach: use NSFetchedResultsController for your main view controller and have the delegate methods take care of updating your table view. No need to fetch, no work to be done in any completion methods - just save the data and the FRC will update your table.
Edit: how to check the physical database
It is possible that your data only exists in memory but is not actually saved to the database. Find the actual database file (in the documents folder of the app from the Simulator) and check it with the sqlite3 command line utility, or with the Firefox plugin "SQLite Manager".
Edit2: more concrete recommendations
You should make sure that you call:
[managedObjectContext save:&error];
Also double-check what your ENTITY macro stands for (not a very smart name).
It seems to me that you are overusing the block methods to no apparent purpose. First try to make everything work on the main thread (one context!). Only if you get performance problems consider background threads and context and calls to performBlock etc.
I have a NSFetchedResultsController to get the data for an UITableView. During the creation of the NSFetchedResultsController I create a NSPredicate that filters the data with an external condition. What's the proper way to refetch the data? Just nil'ing the my __fetchedResultsController and recreating it seems a little bit brutal.
Thanks!
The initWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName: documentation states:
Important: You must not modify fetchRequest after invoking this
method. For example, you must not change its predicate or the sort
orderings.
So you have to recreate the FRC (and call reloadData on the table view) when the predicate changes.
EDIT:
The same "NSFetchedResultsController Class References" also states here:
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:.
So my first answer above is probably wrong. If I understand it correctly:
You cannot modify the fetch request assigned to the FRC.
But you can build a new fetch request (with a new predicate) and assign that to the FRC (controller.fetchRequest = newFetchRequest) if you follow the three steps above.
This means that you don't have to recreate the FRC itself.
(I hope that I got it right now :-)
As indicated in my comment above I figured it out. What you need to do is save a reference to the NSFetchRequest and manipulate that straight away when needed. As a second step, you need to tell your NSFetchedResultsController to fetch it's data again.
I did this by adding two new methods to the default NSFetchedResultsController "stack":
- (void)configureFetchRequest {
NSObject *myExternalDependency = …;
if (!__fetchRequest) {
__fetchRequest = [[NSFetchRequest alloc] init];
}
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EntityName" inManagedObjectContext:self.managedObjectContext];
[__fetchRequest setEntity:entity];
NSPredicate *filter = [NSPredicate predicateWithFormat:#"someValue == %#", [myExternalDependency someProperty]];
[__fetchRequest setPredicate:filter];
}
- (void)performFetch {
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
So what I basically do now is call these two methods on creation of __fetchedResultsController (which I do initialize using __fetchRequest, of course) and every time my external dependency changes. That's it.