How can I make NSFetchedResultsController event correctly when predicate values change? - ios

In my Core Dara model, there is a single Session object that holds a single Order object (though other orders can be floating around in CoreData) and Order holds Purchases. I've got an NSFetchedResultsController that fetches Purchases for the Order in the Session.
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"order.session = %#" argumentArray:#[session];
This works fine for fetching, but it doesn't call back the delegate methods for the fetched results controller in the case that the Order becomes detached from the Session. Is this just a failure in the NSFetchedResultsController, or is there a documented limitation? More importantly, how can I get around that limitation in a clean way?
To be clear, the results controller always returns the correct result after calling performFetch: it's just not firing the delegate methods.

I think this is just a "feature". The fetched results controller tracks changes to its fetched objects, but as far as it's concerned those objects are unchanged - the only change is to the related Order object. I think your best bet would be to use Key Value Observing of your Order object (or Session object) in order to be notified if they become "disconnected".

Related

NSFetchedResultsController notifies its Delegate of delete changes when a managed object is modified, and never notifies for Insert or Update

I have a UITableViewController, which is a delegate for an NSFetchedResultsController. My NSFetchedResultsControllerDelegate functions are set up as per "Typical Use" in Apple's documentation, with the table view controller as the fetched result controller's delegate property.
I also have some view controllers which are presented on top of the table view where the managed objects can be modified. These modifications are done on the same managed object context. There is only one managed object context constructed in my application, and it is accessed globally. (I have put some print statements in my object context construction just to be sure I am not accidentally re-constructing it elsewhere.)
When I modify one of the managed objects, the delegate function controller:didChangeObject is called, but the NSFetchedResultsChangeType is always .Delete. When I create a managed object, the delegate function does not fire at all.
However, when I manually call call performFetch() and tableView.reloadData(), the cells are restored to the correct state: the removed row comes back, any not-inserted rows are created.
The result is that deleting an object works as expected (the cell is removed), but updates to an object cause its cell to be removed, and object creations do not trigger cell inserts.
I tried to create a simple demo of this behaviour, but when I re-created the situation from a blank application, I don't see this behaviour. So something within my application is causing this strange behaviour, but I can't figure out what. Any ideas?
Extra Info:
The actual construction of the predicate and sort descriptors are done over several different classes, but printing them via print(resultsController.fetchRequest.predicate) and print(resultsController.fetchRequest.sortDescriptors) gives the following:
Optional(readState == "2" OR readState == "1")
Optional([(readState, ascending, compare:), (title, ascending, compare:)])
I have put a print statement in my controller:didChangeObject: method, and I can see that this only gets called with type.rawValue = 2 (i.e. .Delete), and only when I modify objects, not when I create them.
It's an inconsistency with how NSFetchedResultsController handles its NSPredicate.
If the NSFetchedResultsController is constructed with a fetch request which has a predicate which does a comparison between an integer and a string like follows:
let predicate = NSPredicate(format: "integerAttribute == %#", String(1))
this will lead to the predicate string being:
integerAttribute == "1"
When this is the case, initial fetches work fine: calling the function performFetch() on the fetched results controller returns all objects where the integerAttribute is equal to 1 (where integerAttribute is of type Int32).
However, the notifications to NSFetchedResultsControllerDelegate do not work fine. Modifications of managed objects result in the delegate being notified of a NSFetchedResultsChangeType.Delete change. Creations of managed objects do not invoke the delegate at all.
To make all this weirdness go away, fix the predicate format string as follows:
let predicate = NSPredicate(format: "integerAttribute == %d", 1)

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.

unattached (disconnect) Core Data entity from context

I'm using magical record for all my core data work.
everything works great, with the exception at times when I'm doing updates in the background I need to detach or disconnect the entity from the context.
for example
ButtonList = [Buttons MR_findAllSortedBy:#"listOrder" ascending:YES];
How would I keep the entity, but remove the reference to the context for the array ButtonList?
Thanks
This will only happen when you don't use a NSFetchedResultsController, or code that observe context changes and remove deleted objects from the UI to reflect the store state.
If you like the deleted objects to be removed from view as soon as your context finds out about the deletion, you would need to listen for "context did change notification" on your main context and look at the deleted objects set, if any of the deleted objects are part of your display array you will need to update your view accordingly (remove from array and update table. a NSFetchedResultsController also listen for context changes).
Another option:
Since you manage your tableview state by yourself (and not a fetched results controller) and
If you like the "buttons" to remain in view including their properties, you could:
Change your request to return dictionaries instead of managed objects (does not nullify on deletion):
NSFetchRequest* r = [Buttons MR_requestAllSortedBy:#"listOrder" ascending:YES];
[r setResultType:NSDictionaryResultType];
//This is your link to the data store and managed object (if you later need to fetch by or update if still exist)
NSExpressionDescription* objectIdDesc = [[NSExpressionDescription new] autorelease];
objectIdDesc.name = #"objectID";
objectIdDesc.expression = [NSExpression expressionForEvaluatedObject];
objectIdDesc.expressionResultType = NSObjectIDAttributeType;
[r setPropertiesToFetch:#[objectIdDesc,#"buttonName",#"buttonIcon"/*, and any other property you need for display*/]];
Now all is left to do is execute this request on any context you like (even in background) and get the array back to your table view controller.
The difference here is you get back dictionaries and not NSManagedObject array.
In Core Data there's no such concept of "detaching" an entity from a given context. You would have tons of problems in attempting to do something like that (e.g. move objects between contexts/persistent stores), especially when dealing with relationships.
I think you should refactor and design your application in a way that reacts proactively to any changes in the object(s) you are representing, e.g by removing the related table view cells (with NSFetchedResultsControllerDelegate callbacks), by automatically popping the detail view controller if present, etc...
I would not recommended recommend it but, if there are states for the object you're representing which are "transitory" and shouldn't be reflected in the UI, you could temporarily nil the NSFetchedResultsController delegate (I assume you're using that to display your managed objects) so that it doesn't receive updates and doesn't update the table view (for example). When the objects are ready to be displayed again, you can set the delegate back and perform a new fetch, so that the tableView gets updated (with automatic cell insertions, removals and updates).
If an object has been fetched from a managed object context, the only way to break its connection to the context is to delete the object and then save changes. There is no way to convert a fetched object into something that's not associated with its context. You could copy the data to some other object, but the one that you fetched is and will always be associated with the context that you got it from.
In your case, if you're deleting these objects in a background queue, you should not be using them any more. Or if you do need to use them, then you should not be deleting them. I can't tell from your question just what you're trying to accomplish here, but what you've described makes no sense.

iOS: How do I add/remove a NSPredicate to my FetchedResultsController from a button press?

I have a button that I currently have set up as a toggle. It currently only changes it's text when pressed. I am wanting to have it filter out entries in my uitableview though. I'm guessing I'd do that by adding a NSPredicate to my fetchedresultscontroller, but I'm unsure how to do this. Anyone know how I might be able to do this or send me in the correct direction if this is not the proper way of doing this task? Thanks!
On button click, you would be require to release the instance of fetchedresultscontroller (if in memory), as you would be require to re-create FetchRequest with Predicate in it.
After that, you could call execute api. As, fetchedresultscontroller will be required a new predicate.
Preferrable is to use 1 level of fetchedResultsController api call from Core Data. Because Fetching from DB is costly as compared to filtering the objects w.r.t already loaded objects in memory.
In your case, use fetchedresultscontroller with fetching all required apis, and as per toggle, just use predicate on in-memory array/records. This will improve performance and secondly, since the records will be core-data managed objects, so if they will be in no-more use, they could get faulted.

Why does my NSFetchedResultsController delegate stop firing when I add an NSPredicate?

I have an NSFetchedResultsController displaying "places" in a table view, but when I update the set of places that should be displayed in another view controller, my FRC does not update the table view.
That's the general problem. My specific case seems to revolve around the NSPredicate backing my FRC, because when I remove the NSPredicate (and just get all places), everything works fine.
My query is
#"ANY photos.isFavorite == %#", [NSNumber numberWithBool:YES]
places have a one-to-many relationship with photos (I am working through CS193P). Perhaps my FRC is not set up to observe changes in a related table or something?
A bit of additional information about my situation:
My Core Data updates and queries seem okay, as my "places" table is always correct when I first load the application.
My FRC does update rows that are already present at application load. It just won't insert new rows/sections at runtime.
I am only using a single MOC.
My sectionNameKeyPath is not set to a transient attribute.
My cacheName is set to nil.
I don't know if it's your case but I'll post anyway. Maybe it could be a valid workaround.
NSFetchedResultsController pitfall
Hope it helps.
Did you try testing a simpler predicate, for example using a photo one-to-one relationship instead of photos?
"my FRC does not update the table view" - Are you using -controllerDidChangeContent: ? Did you verify it is not getting called? I've seen that contrary to the documentation, calling reloadData on the tableView from within this method is unsafe, since the method may be called on a secondary rather than the main thread.
Are you performing all operations related to the MOC on the same (main?) thread?
Try this predicate instead?
#"%# IN photos.isFavorite", [NSNumber numberWithBool:YES]

Resources