Core Data Conundrum: Cannot call NSFetchedResults twice - ios

I have a custom UITableViewController, called mainViewController.
It has a public property in the header file
#property (strong, nonatomic) NSMutableArray *someGroups;
This property is set in the viewDidLoad method
_someGroups = [[GGGroups findAllSortedBy:#"lastUpdated" ascending:NO withPredicate:[NSPredicate predicateWithFormat:#"ANY users = %#", [GGUser currentUser]]] mutableCopy];
However, for some reason, if I ever call the following, once the view has loaded, it returns 0 objects.
[[GGGroups findAllSortedBy:#"lastUpdated" ascending:NO withPredicate:[NSPredicate predicateWithFormat:#"ANY users = %#", [GGUser currentUser]]] mutableCopy];
This is quite annoying especially when I want to reloadData in the table view controller, since this array is the data source, so the table view becomes blank since this method returns 0 objects now.
I am currently using MagicalRecord, does anyone know why this occurs.
Thank you!

There is nothing in MagicalRecord that caches your find* requests. Every call to any of the find methods will generate a new request and perform a new fetch on that new request. My guesses as to why your second fetch is failing:
The default context (which you are implicitly using by not specifying one) is nil somehow
Your currentUser method is returning nil
Your data store is empty on that second call
You aren't actually calling your method to reload data at the right time
You are not refreshing your view of data
MagicalRecord has logging built in. I suggest turning it on and seeing if there are any errors that show up in the logs. Those will help pinpoint any issues that might be arising with Core Data.

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)

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

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".

How to update a NSFetchedResultsController?

I'm building an iOS app using RestKit and NSFetchedResultsController in my ViewControllers like explained in the following tutorial : http://www.alexedge.co.uk/blog/2013/03/08/introduction-restkit-0-20
I have a subclass of a UITableViewController that displays the data fetched by the NSFetchedResultsController and it works well.
Now I want to filter the displayed data so I'm adding a predicate to the fetch request of the NSFetchedResultsController and performing the fetch again (I don't use any cache) :
[self.fetchedResultsController.fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"title like 'some text'"]];
NSError *error = nil;
[self.fetchedResultsController performFetch:&error];
NSAssert(!error, #"Error performing fetch request: %#", error);
No error happens but the table view is not updated. I was expecting this method to be called
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;
but it's not...
Worse, when I log the number of fetched objects like this
NSLog(#"before refetch : %d objects", self.fetchedResultsController.fetchedObjects.count);
[...]
NSLog(#"after refetch : %d objects", self.fetchedResultsController.fetchedObjects.count);
I'm getting
before refetch : 18 objects
after refetch : 0 objects
(That's probably why my app crashes as soon as I try to scroll the table view)
So how should I refresh the content of the NSFetchedResultsController so that controllerDidChangeContent is called and thus I can reload the table view ? and also that fetchedObjects contains something...
EDIT:
If at this moment I call the RestKit object manager to get the data displayed in the table view AND a Internet connection is available THEN the NSFetchedResultsController gets updated and the table view as well with the correct result of the fetch request WITH the predicate.
EDIT:
The predicate I use is the cause of the problem (I think). My entity has a transformable attribute which is parsed from an JSON array of strings and the predicate test if a search string exists in that array:
[NSPredicate predicateWithFormat:#"ANY transformable_attribute like 'search string'"]
The weird thing is that if I load the results without predicate and add it afterwards then I get 0 object BUT if I load the results directly with the predicate then I get 3 objects.
Is my predicate wrong?
EDIT: yes it is! Looks like we can't predicate on transformable attributes. I don't know why it works the first time.
Your predicate is a constraint that leads to 0 results. That is probably expected behavior. Check what is in the data store and you will most likely find that it follows the logic of the predicate and your data.
You will need to change your predicate to get the results you intended.
Call
[self.tableView reloadData];
after performing the fetch to update the table.
Changing the predicate of a NSFetchedREsultsController does not result in a delegate call to controllerDidChangeContent. From the documentation:
An instance of NSFetchedResultsController uses methods in this protocol to notify
its delegate that the controller’s fetch results have been changed due to an add,
remove, move, or update operations.
The solution is to reload the table with
[tableView reloadData]

Core data entity disappears when app resumes from background

I have an app that uses a read-only Core Data database (meaning I never write to the database). I have a view controller that fetches some core data entities and displays them in a table. So for example, I have a core data entity named Photo, and in my view controller I have, say #property (nonatomic, strong) NSArray *photos;, which is a list of Photo objects.
Now everything works fine when the app launches fresh. But when I go to that view and exit the app (so it's still in the background) and reenter it, the #property I've set up becomes nil. I still see all UITableViewCells currently on screen. When I attempt to scroll, though, the app crashes.
I have multiple views that use Core Data stuff, but some of them work just fine. I'm wondering why for some view controllers my Core Data object set up as a #property disappears when the app returns to foreground.
Thanks!
First recommendation: you should be using an NSFetchedResultsController rather than an array. This is much more efficient and will take care of all memory considerations for you, beside many other advantages. As a matter of fact, it was designed specifically to manage the datasource of a table view based on Core Data.
If you still want to use your array, I speculate that you failed to initialize it properly.
In your viewDidLoad method, do not just assign the fetched results from core data but do it with proper initialization:
self.photos = [[NSArray alloc] initWithArray:coreDataResults];
Now the array should persist.
I think I figured out what's wrong with my code. So this is what I did to fetch the core data stuff:
[DatabaseHelper openDatabase:#"database" usingBlock:^(UIManagedDocument *document) {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Shop"];
request.predicate = [NSPredicate predicateWithFormat:#"ecategory = %#", self.category];
NSError *error = nil;
NSArray *matches = [document.managedObjectContext executeFetchRequest:request error:&error];
self.shops = matches;
[self updateTable];
}];
This correctly fetches the results, but the problem is, although I think I retained the Core Data objects, they actually belong to a UIManagedDocumentContext, and I didn't retain that. So when the app returns to foreground, the context is gone, and the core data objects become nil.
So what I did is declare a new property: #property (nonatomic, strong) UIManagedDocument *database; and then set self.database = document; in the block. Now the controller will retain the context and everything works fine.

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