After reading lots of documentation and examples of Core Data framework, I still don't quite get it. What happens is that I sort of understand parts of a sample code or documentation, but often can't fit it into a larger picture. The second time I read the same code, I still need lots of time to figure it out just like the first time. It's so frustrating.
The NSFetchedResultsControllerDelegate vs NSFetchedResultsController is one of those concepts in Core Data that confuse me.
I think what I need is a simple and conceptual explanation, maybe analogy will be helpful.
All your core data objects have to be accessed via managed object context.
Think of NSFetchRequest as a representation of the database query. When you tell the NSManagedObjectContext (MOC) to fetch data, you give it a fetch request, so it knows what to fetch.
Now, let's say you have a table view. You make a fetch, and have all the data, even for the stuff you don't need to display. Each time the table view changes, and you need to refetch the data. There are a few other issues with just using a fetch request, though it can be done.
To solve some of these problems, enter NSFetchedResultsController (FRC). It manages the database so that you only have the objects in memory that are actually needed at the time. Furthermore, it hooks into the MOC so that when the database changes, it automatically changes its own data.
So, you create a FRC and give it a fetch request. Now, it manages the data so it only has in memory what you want. But, you need to tell it what to grab, and it need to tell you when it has data.
Thus is where the NSFetchedResultsControllerDelegate comes in.
The delegate is the glue between the table view (or some other component) and the FRC. The delegate methods are the communication channel that informs the FRC what data to get, when to get it, then hand it off to the table view.
EDIT
Yes, FRC manages the actual data part. However, when its data changes, it has to have some way to notify the guy who is watching the data. That's what the delegate is for. Let's take a different attack by looking at the delegate methods.
Imagine that the database has been changed in some way. The FRC, through its special magical incantations, has noticed the change, and, like a teenager with a new iPhone, needs to tell someone.
Specifically, again, in your case, it needs to tell the table view that is responsible for displaying the data to the user. Well, how is it going to tell the table view that the data has changed? There are actually several patterns used in iOS, and in this case, we use a delegate.
The guy who is interested in receiving this information from the FRC give him a pointer to an object that implements the delegate methods. When the FRC wants to notify the guy interested, it calls the appropriate methods on the object that is was given as the delegate.
Consider a change has happened. The FRC code would look something like this (ultra-simplified but to give the algorithmic idea).
[delegate controllerWillChangeContent:self];
// Process all the changes...
for (SectionChangeInfo *info in changedSections) {
[delegate controller:self didChangeSection:info.sectionInfo atIndex:info.index forChangeType:info.changeType];
}
for (ObjectChangeInfo *info in changedObjects) {
[delegate controller:self didChangeObject:info.object atIndexPath:info.indexPath forChangeType:info.changeType newIndexPath:index.newIndexPath];
}
[delegate controllerDidChangeContent:self];
Thus, the FRC can tell "somebody" about changes when they occur. In your case, when you give the FRC a delegate, it will call those methods, thus giving you a chance to handle the changes when they happen.
The other delegate method is called to ask the delegate what it wants to use as a section title. So, assume the FRC needs to know what to use, it will call...
NSString *sectionTitle = [[section substringToIndex:1] uppercase];
if ([delegate respondsToSelector:#selector(controller:sectionIndexTitleForSectionName:)]) {
sectionTitle = [delegate controller:self sectionIndexTitleForSectionName:section];
}
Related
I'd like to pass to my model the newest fetched data of my core data entities, in order to have them synched.
Is this possible?
The reason is that I have many variables that have to be calculated from the data saved in core data. These values are used in my views, so they should update at the same time.
(Until now I just found a way to pass them around every time with functions, but I find this very chaotic...)
Until now:
func doSomethingWithFetchedData(fetchedData: FetchedResults<Entity>) {
//return what I need
}
Thanks!
NSFetchedResultsController Subscribing to updates for many objects matching a fetch request has been easier than subscribing to updates from a single managed object, thanks to NSFetchedResultsController. It comes with a delegate that informs us about changes to the underlying data in a structured way, because it was designed to integrate with tables and collection views
Here is a good link to start with
I am facing a decision to be made for an applications architecture design.
The Application uses CoreData to persist user information, the same information is also stored on a remote server accessible by a REST-Interface. When the Application starts I provide the cached information from CoreData to be displayed, while I fetch updates from the server. The fetched information is persisted automatically as well.
All of these tasks are performed in background queues as to not block the main thread. I am keeping a strong reference to my persistenContainer and my NSManagedObject called User.
#property (nonatomic, retain, readwrite) User *fetchedLoggedInUser;
As I said the User is populated performing a fetch request via
[_coreDataManager.persistentContainer performBackgroundTask:^(NSManagedObjectContext * _Nonnull context) {
(...)
NSArray <User*>*fetchedUsers = [context executeFetchRequest:fetchLocalUserRequest error:&fetchError];
(...)
self.fetchedLoggedInUser = fetchedUsers.firstObject;
//load updates from server
Api.update(){
//update the self.fetchedLoggedInUser properties with data from the server
(...)
//persist the updated data
if (context.hasChanges) {
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
NSError *saveError = nil;
BOOL saveSucceeded = [context save:&saveError];
if (saveSucceeded) {
//notify the App about the updates
//**here is the problem**
}
};
}];
So the obvious thing about this is, that after performing the backgroundTask, my self.fetchedLoggedInUser is not in memory anymore, because of its weak reference to the NSManagedObjectContext provided by the performBackgroundTask() of my PersistentContainer.
Therefore, if I try to access the information from another Model, the values are nil.
What would be the best practice to keep the fetched ManagedObject in Memory and not have to fetch it again, every time I want to access its values?
A) In the Documentation, Apple suggests using the objectID of an ManagedObject, to pass objects between queues
Passing References Between Queues
NSManagedObject instances are not intended to be passed between
queues. Doing so can result in corruption of the data and termination
of the application. When it is necessary to hand off a managed object
reference from one queue to another, it must be done through
NSManagedObjectID instances.
You retrieve the managed object ID of a managed object by calling the
objectID method on the NSManagedObject instance.
The perfectly working code for that situation would be to replace the if(saveSucceeded) Check with this Code:
if (saveSucceeded) {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error = nil;
self.fetchedLoggedInUser = [self.coreDataManager.persistentContainer.viewContext existingObjectWithID:_fetchedLoggedInUser.objectID error:&error];
(...)
//notify the App about the updates
});
}
But I think this may not be the best solution here, as this needs to access the mainContext (in this case the persistentContainer's viewContext) on the mainQueue. This is likely contradictory to what I am trying to do here (performing on the background, to achieve best performance).
My other options (well, these, that I came up with) would be
B) to either store the user information in a Singleton and update it every time the information is fetched from and saved to CoreData. In this scenario I wouldn't need to worry about keeping the NSManagedObject context alive. I could perform any updates on a private background context provided by my persistentContainer's performBackgroundTask and whenever I'd need to persist new / edited user information I could refetch the NSManagedObject from the database, set the properties, save my context and then update the Singleton. I don't know if this is elegant though.
C) edit the getter Method of my self.fetchedLoggedInUser to contain a fetch request and fetch the needed information (this is probably the worst, because of the overhead when accessing the database) and I am not even sure if this would work at all.
I hope that one of these solutions is actually best practice, but I'd like to hear your suggestions why/how or why/how not to handle the passing of the information.
TL:DR; Whats the best practice to keep user information available throughout the whole app, when loading and storing new information is mostly done from backgroundQueues?
PS: I don't want to fetch the information every time I need to access it in one of my ViewControllers, I want to store the data on a central knot, so that it is accessible from every ViewController with ease. Currently the self.fetchedLoggedInUser is a property of a singleton used throughout the application. I find that this saves a lot of redundant code, using the Singleton makes loading and storing the information clearer and reduces the access count to the database. If this is considered bad practice I'd be happy to discuss about that with you.
Use a NSFetchedResultsController - they are very efficient and you can use them even for one object. A FetchedResultsController does a fetch once and then monitors core data for changes. When it changes you have a callback that it has changed. It also works perfectly for ANY core-data setup. So long as the changes are propagated to the main context (either with newBackgroundContext or performBackgroundTask or child contexts or whatever) the fetchedResultsController will update. So you are free to change your core-data stack without changes your monitoring code.
In general I don't like keeping pointers to ManagedObjects. If the entry is deleted from database then the managedObject will crash when you try to access it. A fetchedResultsController is always safe to read fetchedObjects as it tracks deletions for you.
Obviously attach the NSFetchedResultsController to the viewContext and only read it from the main thread.
I came up with a very elegant solution in my opinion.
From the beginning I was using a Singleton called sharedCoreDataManager, I added a property backgroundContext that is initialized like so
self.backgroundContext = _persistentContainer.newBackgroundContext;
_backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_backgroundContext.retainsRegisteredObjects = YES;
and is retained by sharedCoreDataManager. I am using this context to perform any tasks. Through calling _backgroundContext.retainsRegisteredObjects my NSManagedObject is retained by the backgroundContext, which is itself (like I said) retained by my Singleton sharedCoreDataManager.
I think this is an elegant solution as I can access the ManagedObject threadsafe from the background anytime. I also won't need any extra class that holds the user information on top. And on top of that I can easily edit the user information at anytime and then call save() on my backgroundContext if needed.
Maybe I am going to add it as a child to my viewContext in the future, I'll evaluate the performance and eventually update this answer.
You are still welcome to propose a better solution or discuss this topic.
I'm looking to integrate iCloud with a Core-Data-managed SQLite database (only on iOS 7 and later). I've been reading Apple's guide on using Core Data with iCloud (https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/UsingCoreDataWithiCloudPG.pdf).
To quote from the guide, "Core Data posts an NSPersistentStoreCoordinatorStoresWillChangeNotification notification. In your notification handler, you reset your managed object context and drop any references to existing managed objects."
Calling -reset on the MOC to reset it isn't the problem, the problem is the part where they say all references to managed objects need to be dropped. I understand why this needs to be done (because the persistent store is changing), what I don't know is how to do it.
All my Core Data work is handled by a singleton and I had originally thought of posting a notification, and listening classes could set all their managed objects to nil. First, this doesn't sound like a particularly good way of doing it. Secondly, I have a FetchedResultsController managing a tableView, the FetchedResultsController manages it's own managed objects, therefore, as far as I know, I can't set them to nil.
I'd be really grateful for any advice on what to do here.
Thanks in advance.
The way I handle situations like this is to post two notifications in my app: just before resetting, and just after resetting.
For example, I might post MYMainContextWillResetNotification, then reset the context, then post MYMainContextDidResetNotification.
Any controller receiving the will-reset notification should release its managed objects, but also store any information it will need to recover after the reset. Usually this will be one or more NSManagedObjectID objects. In some cases, you may not need to store anything, simply performing a fetch after the reset instead.
A typical method might look like this:
- (void)mainContextWillReset:(NSNotification *)notif
{
self->noteID = note.objectID;
}
This code supposes there is a controller for a single note object. When the reset is about to take place, the note's object identifier is stored in an instance variable.
The did-reset notification method retrieves the note.
- (void)mainContextDidReset:(NSNotification *)notif
{
note = [context existingObjectWithID:noteID error:NULL];
[self refreshViews];
}
This code uses existingObjectWithID:error:, but you could equally do a fetch.
With an NSFetchedResultsController, you would need to call performFetch: in the did-reset method, to refresh the objects.
I am working on a VOIP/Chat application that receives data even while in the background.
I need to disable the NSFetchedResultsController when the app is moving to the background to prevent UI changes in the background.
I do it like this -
- (void)applicationWillResignActive
{
[super applicationWillResignActive];
self.fetchedResultsController.delegate = nil;
}
- (void)applicationDidBecomeActive
{
[super applicationDidBecomeActive];
self.fetchedResultsController.delegate = self.fetchResultControllerDelegate;
}
I have noticed that I don't need to call [self.tableView reloadData] when coming back to foreground. (EDIT : just to clarify the Core Data DB was updated with new data while the app was in the background and the fetchedResultsController.delegate was nil).
And the table updates itself right after reassigning the fetchedResultsController.delegate.
What makes it update, does the fetchedResultsController preforms fetch when reassigned ?
Are there any pitfalls to this approach that can make a conflict between the tableView and the fetchedResultsController ?
Thanks
Fetched results controllers provide the following features:
Optionally monitor changes to objects in the associated managed object context, and report changes in the results set to its delegate (see “The Controller’s Delegate”).
Optionally cache the results of its computation so that if the same data is subsequently re-displayed, the work does not have to be repeated (see “The Cache”).
A controller thus effectively has three modes of operation, determined by whether it has a delegate and whether the cache file name is set.
No tracking: the delegate is set to nil.
The controller simply provides access to the data as it was when the fetch was executed.
Memory-only tracking: the delegate is non-nil and the file cache name is set to nil.
The controller monitors objects in its result set and updates section and ordering information in response to relevant changes.
Full persistent tracking: the delegate and the file cache name are non-nil.
The controller monitors objects in its result set and updates section and ordering information in response to relevant changes. The controller maintains a persistent cache of the results of its computation.
What makes it update, does the fetchedResultsController preforms fetch when reassigned ?
It only does a fetch the first time you create a fetched results controller. After that, it is notified of any change to any object in the database as the changes happen, and evaluates whether to add or remove the object to the list of results. It never does a second fetch (except maybe if you get a low memory warning?).
Fetches are slow, it has to read flash memory and that takes milliseconds. However when an object is being changed, it's in RAM so operations only take nanoseconds. This is why it tries hard not to ever look at the sqlite database.
Are there any pitfalls to this approach that can make a conflict between the tableView and the fetchedResultsController ?
I'm not sure, maybe. If you haven't found any problems in testing, then I think you should assume it's fine. But be sure to test it again carefully when iOS 8 betas are available mid next year, so you can fix any problems before it's released to the public.
If you're worried about RAM usage, you should destroy the fetched results controller. But beware it will take a fairly significant amount of time to create it again, when the user switches back to your app.
Struggling a bit here...
My view controller adheres to following protocols
In my init method I will check a remote server to get an updated XML file... parse the XML file, and write the contents to Core Data.
My tableview's content is managed with NSFetchedResultsController that displays this Core Data.
My Problem:
NSFetchedResultsController seems to be getting the data before the Core Data update from the remote file takes place. I've verified the database is being updated properly and if I run a second time the TableView will show the correct data.
Maybe I'm just not doing the reloadData in the proper place? I have implemented
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[myTableView reloadData];
}
Also, after the parser completes and the new data has been written to core data I'm trying this:
-(void)parserDidEndDocument:(NSXMLParser *)parser {
[myTableView reloadData];
}
Anyone have any ideas? Let me know what extra code might be useful to post. Thanks!
you may want to check the following (from Apple's docs)
A controller thus effectively has three modes of operation, determined by whether it has a delegate and whether the cache file name is set.
No tracking: the delegate is set to nil.
The controller simply provides access to the data as it was when the fetch was executed.
Memory-only tracking: the delegate is non-nil and the file cache name is set to nil.
The controller monitors objects in its result set and updates section and ordering information in response to relevant changes.
Full persistent tracking: the delegate and the file cache name are non-nil.
The controller monitors objects in its result set and updates section and ordering information in response to relevant changes. The controller maintains a persistent cache of the results of its computation.
it sounds like you want full persistent tracking. So you probably want to make sure you have the delegate set (which you probably already have done) and set the cache to non nil
You may also want to make sure you are saving your managedObjectContext after you are done parsing. After saving, make sure to perform the fetch again.
NSError *error;
BOOL success = [controller performFetch:&error];
if (!success)
NSLog(#"Core Data Fetch Error: %#"error);
It could be that the app is saving the context when it is exiting and that is why you are seeing the data when you relaunch.
Good luck