Object returned from fetch request has incorrect data - ios

I am attempting to get an object from a fetch request, and I am checking a property of that object.
Depending on what type the property is will cause me to display a notification or not. After making a successful connection, I set the property type of my object to 'updated' from 'inserted.' Then, when I refresh my view, I pull all objects from coredata and check their properties for the 'updated' type. The problem I am having is that the objects returned in my fetch request that I just attempted to change to 'updated' still display the old 'inserted' value from the fetch request, but don't immediately after the submission. Its like they are reverting. (and I AM saving the context)
What is even more confusing is I have gotten a program to look at the actual tables in the database file stored on the device, and it actually shows the correct value of updated in the table. But the fetch request still comes back with the object having incorrect data. And no amount of refreshing fixes the issue.
How can a fetch request be giving me objects with old/incorrect data when the coredata file shows the tables with correct values?
// code for the fetch request
// return an array of all assets for a specific customer
NSFetchRequest *fetchReq = [NSFetchRequest fetchRequestWithEntityName:#"Asset"];
fetchReq.predicate = [NSPredicate predicateWithFormat:#"customerID = %#" argumentArray:[NSArray arrayWithObject:customerID]];
NSArray *results = [[CoreDataManager sharedManager] executeFetchRequest:fetchReq];
return results;
//executeFetchRequest method
NSManagedObjectContext *context = [self managedObjectContextForCurrentThread];
NSArray *results = [context executeFetchRequest:fetchRequest error:&error];
return results;

#Arcanfel's suggestion (in comments to the original question) certainly helped me find the solution to my own problem, but it was hidden in the 'Show more comments' section.
It would have been helpful to see that as an answer, not a comment, so I've taken his comment and expanded it a little.
Original comment:
[self managedObjectContextForCurrentThread] - I think this can cause a
problem, as different contexts can have different version of
NSManagedObjects. You can call [fetchReq
setShouldRefreshRefetchedObjects:YES] and it will return the most
up-to-date version of your objects
Whilst using the setShouldRefreshRefetchedObjects:YES didn't resolve my problem of subsequent fetch requests not bringing back the right data, it did make me look at my context management.
In one method I was setting the context, and then reading data. In another method I was setting another context, reading data, altering it and then saving that context... a different context to what my first method had.
In short, be careful that you're referring to the same context or anything you update in context1 won't be in synch with what you retrieve from context2.

Related

Fetch data from context that is not yet committed. CoreData

I have created multiple instances of an NSManagedObject entity(for example Car: NSManagedObject) in the default NSManagedObjectContext using MagicalRecord.
I didn't save the context. Is there a way to execute a fetch request and obtain the data that is already in persistent state and the data not yet committed that was added in default context ?
Yes, it is fetched. Please check Apple docs at https://developer.apple.com/reference/coredata/nsmanagedobjectcontext:
An object that meets the criteria specified by request (it is an instance of the entity specified by the request, and it matches the request’s predicate if there is one) and that has been inserted into a context but which is not yet saved to a persistent store, is retrieved if the fetch request is executed on that context.
not as far as I know... you can fetch it all and then look at the objects objectID to decide which one was already saved.
id all = [ctx fetch..];
id savedOnly = [NSMutableArray array];
for(id o in all) {
if([[o objectID] isTemporary] == NO) {
[savedOnly addObject:o];
}
}
OR change your code to use to contexts -- that may be better :D
OR maybe use a predicate like:
savedOnly = [ctx fetchWithPredicate:#"... self.objectID.isTemporary=NO"];
MIGHT work... don't know
If we are talking about the same managed object context - yes, you should be able to get also those objects, which are not committed. This is the default behavior. The managed object context contains also the uncommitted objects, i.e. the objects which are not saved yet in the persistent store.

Core Data fetch predicate nil check failing/unexpected results?

I have a Core Data layer with several thousand entities, constantly syncing to a server. The sync process uses fetch requests to check for deleted_at for the purposes of soft-deletion. There is a single context performing save operations in a performBlockAndWait call. The relationship mapping is handled by the RestKit library.
The CoreDataEntity class is a subclass of NSManagedObject, and it is also the superclass for all our different core data object classes. It has some attributes that are inherited by all our entities, such as deleted_at, entity_id, and all the boilerplate fetch and sync methods.
My issue is some fetch requests seem to return inconsistent results after modifications to the objects. For example after deleting an object (setting deleted_at to the current date):
[CoreDataEntity fetchEntitiesWithPredicate:[NSPredicate predicateWithFormat:#"deleted_at==nil"]];
Returns results with deleted_at == [NSDate today]
I have successfully worked around this behavior by additionally looping through the results and removing the entities with deleted_at set, however I cannot fix the converse issue:
[CoreDataEntity fetchEntitiesWithPredicate:[NSPredicate predicateWithFormat:#"deleted_at!=nil"]];
Is returning an empty array in the same conditions, preventing a server sync from succeeding.
I have confirmed deleted_at is set on the object, and the context save was successful. I just don't understand where to reset whatever cache is causing the outdated results?
Thanks for any help!
Edit: Adding a little more information, it appears that once one of these objects becomes corrupted, the only way get it to register is modifying the value again. Could this be some sort of Core Data index not updating when a value is modified?
Update: It appears to be a problem with RestKit https://github.com/RestKit/RestKit/issues/2218
You are apparently using some sintactic sugar extension to Core Data. I suppose that in your case it is a SheepData, right?
fetchEntitiesWithPredicate: there implemented as follows:
+ (NSArray*)fetchEntitiesWithPredicate:(NSPredicate*)aPredicate
{
return [self fetchEntitiesWithPredicate:aPredicate inContext:[SheepDataManager sharedInstance].managedObjectContext];
}
Are you sure that [SheepDataManager sharedInstance].managedObjectContext receives all the changes that you are making to your objects? Is it receives notifications of saves, or is it child context of your save context?
Try to replace your fetch one-liner with this:
[<your saving context> performBlockAndWait:^{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"CoreDataEntity"];
request.predicate = [NSPredicate predicateWithFormat:#"deleted_at==nil"];
NSArray *results = [<your saving context> executeFetchRequest:request error:NULL];
}];
First, after a save have you looked in the store to make sure your changes are there? Without seeing your entire Core Data stack it is difficult to get a solid understanding what might be going wrong. If you are saving and you see the changes in the store then the question comes into your contexts. How are they built and when. If you are dealing with sibling contexts that could be causing your issue.
More detail is required as to how your core data stack looks.
Yes, the changes are there. As I mentioned in the question, I can loop through my results and remove all those with deleted_at set successfully
That wasn't my question. There is a difference between looking at objects in memory and looking at them in the SQLite file on disk. The questions I have about this behavior are:
Are the changes being persisted to disk before you query for them again
Are you working with multiple contexts and potentially trying to fetch from a stale sibling.
Thus my questions about on disk changes and what your core data stack looks like.
Threading
If you are using one context, are you using more than one thread in your app? If so, are you using that context on more than one thread?
I can see a situation where if you are violating the thread confinement rules you can be corrupting data like this.
Try adding an extra attribute deleted that is a bool with a default of false. Then the attribute is always set and you can look for entities that are either true or false depending on your needs at the moment. If the value is true then you can look at deleted_at to find out when.
Alternatively try setting the deleted_at attribute to some old date (like perhaps 1 Jan 1980), then anything that isn't deleted will have a fixed date that is too old to have been set by the user.
Edit: There is likely some issue with deleted_at having never been touched on some entities that is confusing the system. It is also possible that you have set the fetch request to return results in the dictionary style in which case recent changes will not be reflected in the fetch results.

Deleting Core Data after X amount of days

So I have a bunch of objects in Core Data and want them to auto delete after X amount of days (this would be based off of an NSDate). I did some searching and it seems that you can only delete one core data object at a time, not a group of them, let alone ones that are based off of a certain date. I'm thinking maybe to have a loop running going through each object - but that seems like it would be very processor heavy. Any ideas on where I should be looking to do this? Thanks.
A loop deleting objects one by one is the correct approach.
Deleting objects in Core Data is extremely processor heavy. If that's a problem, then Core Data is not suitable for your project, and you should use something else. I recommend FCModel, as a light weight alternative that is very efficient.
If you are going to stick with Core Data, it's a good idea to perform large operations on a background NSOperationQueue, so the main application is not locked up while deleting the objects. You need to be very careful with Core Data across multiple threads, the approach is to have a separate managed object context for each thread, both using the same persistent store coordinator. Do not ever share a managed object across threads, but you can share the objectID, to fetch a second copy of the same database record on the other managed object context.
Basically your background thread creates a new context, deletes all the objects in a loop, then (on the main thread preferably, see documentation) save the background thread context. This will merge your changes unless there is a conflict (both contexts modify the same object) — in that scenario you have a few options, I'd just abort the entire delete operation and start again.
Apple has good documentation available for all the issues and sample code available here: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdConcurrency.html
It's a bit daunting, you need to do some serious homework, but the actual code is very simple once you've got your head around how everything works. Or just use FCModel, which is designed for fast batch operations.
It's not as processor heavy as you may think :) (of course it depends of data amount)
Feel free to use loop
- (void)deleteAllObjects
{
NSArray *allEntities = self.managedObjectModel.entities;
for (NSEntityDescription *entityDescription in allEntities)
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entityDescription];
fetchRequest.includesPropertyValues = NO;
fetchRequest.includesSubentities = NO;
NSError *error;
NSArray *items = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *managedObject in items) {
[self.managedObjectContext deleteObject:managedObject];
}
if (![self.managedObjectContext save:&error]) {
NSLog(#"Error occurred");
}
}
}
As others have noted, iterating over the objects is the only way to actually delete the objects in Core Data. This is one of those use cases where Core Data's approach kind of falls down, because it's just not optimized for that kind of use.
But there are ways to deal with it to avoid unwanted delays in your app, so that the user doesn't have to wait while your code chugs over a ton of delete requests.
If you have a lot of objects that need to be deleted and you don't want to have to wait until the process is complete, you can fake the initial delete at first and then later do the actual delete when it's convenient. Something like:
Add a custom boolean attribute to the entity type called something like toBeDeleted with a default value of NO.
When you have a bunch of objects to delete, set toBeDeleted to YES on all of them in a single step by using NSBatchUpdateRequest (new in iOS 8). This class is mostly undocumented, so look at the header file or at the BNR blog post about it. You'll specify the property name and the new attribute value, and Core Data will do a mass, quick update.
Make sure your fetch requests all check that toBeDeleted is NO. Now objects marked for deletion will be excluded when fetching even though they still exist.
At some point-- later on, but soon-- run some code in the background that fetches and deletes objects that have toBeDeleted set to YES.

NSSortDescriptor not being applied to fetch request results under iOS 5.1 (but works fine under iOS 6+)

I have a fetch request "foo" on which I have a sort descriptor (it sorts an Integer 16 attribute in descending order). I execute the fetch request thusly:
__block NSArray *results = nil;
[_managedObjectContext performBlockAndWait:
^{
results = [_managedObjectContext executeFetchRequest:<foo> error:nil];
}];
return results;
When I execute this fetch request under iOS 6+, the results are sorted as specified/expected (reverse integer index order).
However, when I execute it under iOS 5.1, the results are unsorted!
But if I then immediately apply the exact same sort descriptor to the results array thusly:
results = [results sortedArrayUsingDescriptors:<foo>.sortDescriptors];
the results are then sorted correctly.
Has anyone else encountered anything like this? If so, did you discover the cause?
Thanks!
Carl
P.S.: In case it proves relevant, here is the definition of "foo":
-(NSFetchRequest *)fetchRequestForUnparsedStoriesOfStoryListWithId:(id)storyListId
{
NSPredicate *hasStoryListWithIdPredicate = [NSPredicate predicateWithFormat:#"ANY lists.id = %#", storyListId];
NSPredicate *isUnparsedStoryOfStoryListWithIdPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:#[hasStoryListWithIdPredicate, self.isUnparsedPredicate]];
NSFetchRequest *fetchRequestForUnparsedStoriesOfStoryListWithId = [NSFetchRequest fetchRequestWithEntityName:#"Story"];
fetchRequestForUnparsedStoriesOfStoryListWithId.predicate = isUnparsedStoryOfStoryListWithIdPredicate;
fetchRequestForUnparsedStoriesOfStoryListWithId.sortDescriptors = [NSArray arrayWithObject:self.descendingIndexSortDescriptor];
return fetchRequestForUnparsedStoriesOfStoryListWithId;
}
and here is the definition of the sort descriptor:
-(NSSortDescriptor *)descendingIndexSortDescriptor
{
if (! _descendingIndexSortDescriptor)
{
self.descendingIndexSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"index" ascending:NO];
}
return _descendingIndexSortDescriptor;
}
P.P.S.: A little more context on, well, the context of this fetch: This is occurring on app launch, after 1) a background-thread/background-MOC (a nested MOC with NSPrivateQueueConcurrencyType) parse of server information has determined some basic properties, then 2) created a set of Story entities in the background MOC having (inter alia) the index being sorted upon, and then 3) saved the changes on that background MOC. Only, and immediately, after those operations complete is the above fetch request performed, and it is performed on the same background thread and background MOC. Having read here: NSSortdescriptor ineffective on fetch result from NSManagedContext — that "unless the data is saved all the way back to Persistent Store, the sorting won't work if the data in the master context is dirty, i.e. modified", I tried forcing the save in step 3) all the way "up" the nested MOC chain to the persistent store (i.e., to disk), but that too had no effect: under iOS 5.1 (only) the results of this fetch are not sorted, unless I sort the results "manually" after performing the fetch.
OK, after further experimentation, I've determined that indeed, in iOS 5.1 (but not in iOS 6+), as stated here: NSSortdescriptor ineffective on fetch result from NSManagedContext — "unless the data is saved all the way back to Persistent Store, the sorting won't work if the data in the master context is dirty, i.e. modified". If I save all entity changes (insertions, modifications, deletions) to the persistent store (performing synchronous saves all the way up the nested MOC chain to the disk-MOC) before executing the fetch request, the results are properly sorted. If I don't then the results are not sorted.
So: if your app needs to run under iOS 5.1, and if you use nested MOCs, you must either 1) save all the way "up" to your persistent store (e.g. disk) before executing a sorted fetch request, or 2) apply the sort yourself on the array of fetched results.
(Even though this problem is in fact answered in the linked post, I'm going to leave this question and answer up here, until/unless requested to remove it, because I think it is clearer and more explicit as to exactly what the problem and its solution is.)
You probably don't want to use performBlockAndWait this way. It's only intended for reentrancy, and as such it can't do some of the things that performBlock does for you - like autorelease pools and user events. User events are EXTREMELY important for core data to function as you expect. For example, changes made inside performBlockAndWait will not trigger NSManagedObjectContext notifications! See the iOS 5 Core Data release notes for more information.
There are a number of known issues with CoreData on iOS 5.x that are fixed on iOS 6. Too many to list. There was one where the sorting would not work if there were changes in the parent context, and I do think there was another regarding sorting. Unfortunately I no longer have the radar numbers for these.
Do you get the same sorting issue if you use performBlock with a semaphore in place of performBlockAndWait, as illustrated in WWDC 2012 session 214 "Core Data Best Practices"?

Using Core Data in app without UITableView

I have a simple to do list application. It uses dynamically generated text fields spaced programmatically for the tasks (I didn't use UITableView because of some custom animations and whatnot that I want to use).
In the app the user can create multiple lists (home, work, school, etc.) each with their own tasks.
I want to use Core Data to store the information
Saving the information is straightforward enough but updating my Core Data objects (List and Task) are where I'm getting stuck. Also how, with Core Data, to associate in a specific tasks with a specific list.
let me know if I need to clarify anything.
Your best bet is NSFetchedResultsController. You can use it exactly like in the pattern suggested by the Xcode templates (you can look at it by creating a new project Master-Detail and checking "User Core Data").
You can device your object model (entity Task) with a string attribute for the name as well as a NSNumber boolean attribute for done etc. I also recommend a timestamp and maybe a serial number for ordering it (I find NSOrderedSet unreliable). Your entity List should have a to-many relationship to Task. Pretty straight forward.
List <---->> Task
The only difference is now to find the right object, because you cannot use objectAtIndexPath. You can use the index in fetchedResultsController.fetchedObjects for that. Just make sure your objects are ordered as expected.
I'm not totally clear on your question, however, the task of updating a managed object is straightforward. When you're doing an initial add (similar to an "insert" in SQL) you might use code like this:
NSManagedObject *obj;
obj = [NSEntityDescription insertNewObjectForEntityForName:#"UserData" inManagedObjectContext:context];
[obj setValue:user forKey:#"userName"];
[obj setValue:goalCategory forKey:#"goalCategory"];
[obj setValue:goalDetail forKey:#"goalDetail"];
NSError __autoreleasing error;
[context save:&error];
That's about it for inserting a new item. For updating after you're found the managed object you're working on, you just change the values in the managed object and use [context save:&error]. Here's an example:
UserData *uData = (UserData *) managedObj;
uData.itemName = nameText;
NSError __autoreleasing *error;
[context save:&error];
That's pretty much it.
As to the update, once you have selected the object(s) to be updated, they are contained in
fetchedResultsController.fetchedObjects
which is an NSArray. So, you might do something like this:
UserData *uData = (UserData *) [fetchedResultsController.fetchedObjects objectAtIndex:3];
uData.completed = YES;
NSError __autoreleasing *error;
[context save:&error];
So, this would update the field completed in the UserData entity to be == YES for the object at index 3 in the fetchedObjects array.
I know there are other methods of updating and lots of options but I haven't found any need for them. fetchedObjects is an array containing the items returned by your fetch; to update them, cast each object to the entity (which is defined as a NSManagedObject), make the change then context save..
HTH.
First of all, think is it good idea to use Core Data for your project. If your model is light and simple, maybe it will be better to use plists.
If you choose Core Data, just remember 2 rules:
Each thread owns separate NSManagedObjectContext;
Perform operations with context only in its thread.
And don't worry about optimizations now. Realize any scheme of updating your storage. Make sure it works. And then you should try some other update methods.

Resources