Is it necessary to cache data returned from CoreData? - iphone

I store data in an iPhone app using CoreData. The data stored is intended to represent a tree, with each record being a node (with pointer to its parent). As the user navigates through this tree, I cache the current parent and current set of children in an NSArray. I initially thought this made sense because it could allow quicker access of sibling nodes (useful when the data is presented in a UITableView and the user scrolls). I felt that using CoreData to perform a "get all children" query each time the next child is required would be inefficient (i.e. I'd be making the same query n times for n children, and just returning the ith result each time).
Now, I am not so sure this is necessary nor smart (mostly because I've learned that CoreData is a beast I have yet to fully understand).
Does anybody know whether this is good practice? I know CoreData does a lot for free including the caching of results, but since I know my data is going to be used like a tree, would my implementation (or some other outside-of-CoreData cache implementation) be more efficient?

when you use coredata and an UITableView you should use a NSFetchedResultsController. It does all the caching for you, and for sure it does it better than you.
the fetchedResultsController also takes care of deletion, changing and insertion of coredata objects, ie it tells you when to update the tableview. but for the latter to function you have to set up the fetchedresultscontroller delegate methods. there is a complete example of the delegate functions included in the documentation.
in general, get rid of your caching, it's not needed.

Related

Alternative to NSFetchedResultsController?

Currently I have a UITableView with its data source being an NSFetchedResultsController. The most important thing the NSFetchedResultsController does is automatically update my table if there are any changes, via delegate methods. However, I no longer need to do a fetch to get my entity, call it "Pictures" for now. I have another entity called Folder, and folders have a relationship with Pictures, so every folder has an NSSet pictures.
So instead of fetching all pictures that belong to a certain folder, now I can just do folder.pictures, and that returns what I need, and I can assign that to an array and set that as my tableView source. However, this doesn't give me automatic table updates like an NSFetchedResultsController would.
My question is how can I have the functionality of an NSFetchedResultsController (that is, the delegate methods that automatically update my table) without executing a fetch? I don't need to fetch anymore since I have an NSSet with the desired NSManagedObjects.
What's wrong with the fetched results controller? Just keep it and use the dot notation for relationship sets as well - you get the best of both worlds.
The real advantage of the fetched results controller is actually hidden. It will fetch your objects (folders) alright - but maybe it will not fetch all the relationship attributes (pictures). This is called faulting. It means that core data will get the data in the background if it is needed. It is automatically optimized for speed and good memory usage. For example, the potentially huge array of your datasource will not have to be all in memory at once, something that is unavoidable with an array.
Thus, you really do not want to get rid of the FRC. She is your friend. Stay faithful to her. ;-)

NSFetchedResultsController does anyone have any insights into the cache implementation?

this is a bit of an odd question, so I'll start at the beginning...
My needs for NSFetchedResultsController (NSFRC) are the ability to perform filtering and sorting after the objects have been fetched, mostly because the filtering and sorting require querying the fetched objects themselves, and is therefore not possible with NSFRC. So, I wrote my own class, BSFetchedResultsController, which aims to replicate the functionality of NSFRC (delegate notifications, automatic sectioning and caching) but with added hooks for the user to set their own blocks for filtering and sorting. The code for this class is on github here if anyone wants it: https://github.com/blindingskies/BSFetchedResultsController, although I wouldn't consider the class ready yet as a drop in replacement of NSFRC.
So, I've not yet implementing caching, mostly because I'm not really sure how Apple has implemented it. The caches are stored in binary files here:
{app dir}/Library/Caches/.CoreDataCaches/SectionInfoCaches/{cache name}/sectionInfo
So, presumably, my class would need to store its caches in a similar location? How is this structure organised/work? The cache needs to store the NSFetchPredicate (or properties required to re-generate it), and it needs to archive the fetched objects somehow. But, NSManagedObject doesn't conform to NSCoding, so, how does it archive the objects? And lastly during the NSNotificationCenterDidChangeNotification handler the cache needs to be updated.
So, the real aspect of this is how to archive the fetched objects, I'm leaning towards just saving the objectIDs in an array? And then just get those objects from the context. Is that enough?
If anyone has thought about how to implement
Okay, so to answer my own question, I've implemented the cache as follows:
Created another class which retains the entity (NSEntityDescription), fetch predicate (NSPredicate) and sort descriptors (NSArray) of the NSFetchPredicate, along with the sectionNameKeyPath and additional BSFetchedResultsController objects (post fetch predicate, filter, comparator). Make this class NSCoding compliant.
Then at the start of the performFetch: method, if there is a cache name, unarchive the object and see if the properties match the BSFRC, and if it does, then use the cache's section data.
Then add another notification handler, to NSManagedObjectContextDidSaveNotification to flush the objects to the cache.
A couple of points... I found that archiving the NSFetchRequest directly (which is NSCoding compliant) didn't work, and at the moment, am only checking the name of the NSEntityDescription.
Also, I don't cache the whole object graph, just the URIRepresentation of the NSManagedObject's NSManangedObjectIDs. Then, I respawn these URIs given the managed object context after validating the cache.
It seems to work, although I'm not sure how often I should flush the objects to the cache...

Using NSFetchedResultsController Without UITableView

Is it wrong to use an NSFetchedResultsController purely for data management, i.e., without using it to feed a UITableView?
I have a to-many relationship in a Core Data iPhone app. Whenever data in that relationship changes, I need to perform a calculation which requires that data to be sorted. In Apple's standard Department/Employees example, this would be like determining the median salary in a given Department. Whenever an Employee is added to or removed from that Department, or an Employee's salary changes, the median calculation would need to be performed again.
Keeping data sorted and current and getting notifications when it changes sounds like a great job for NSFetchedResultsController. The only "problem" is that I'm not using a UITableView. In other words, I'm not displaying sorted Employees in a UITableView. I just want an up-to-date sorted array of Employees so I can analyze them behind the scenes. (And, of course, I don't want to write a bunch of code that duplicates much of NSFetchedResultsController.)
Is it a bad idea to use an NSFetchedResultsController purely for data management, i.e., without using it to feed a UITableView? I haven't seen this done anywhere, and thought I might be missing something.
I would not call it bad but definitely "heavy".
It would be less memory and CPU to watch for saves via the NSManagedObjectContextDidSaveNotification and do the calculation there. The notification will come with three NSArray instances in its userInfo and you can then use a simple NSPredicate against those arrays to see if any employee that you care about has changed and respond.
This is part of what the NSFetchedResultsController does under the covers. However you would be avoiding the other portions of the NSFetchedResultsController that you don't care about or need.
Heavy
NSFetchedResultsController does more processing than just watch for saved objects. It handles deltas, makes calls to its delegates, etc. I am not saying it is bad in any way shape or form. What I am saying is that if you only care about when objects have changed in your relationship, you can do it pretty easily by just watching for the notifications.
Memory
In addition, there is no reason to retain anything since you are already holding onto the "Department" entity and therefore access its relationships. Holding onto the child objects "just in case" is a waste of memory. Let Core Data manage the memory, that is part of the reason for using it.
There's nothing wrong with using NSFetchedResultsController without a view. Your use case sounds like a good reason to not re-invent the wheel.
To me, this sounds like an appropriate use of NSFetchedResultController. it might be a bit overkill, as its primary use IS to help populate and keep up to date tableViews, but if you are willing to put up with the added complexity, there is no reason to not use it as such. Correct use of notifications would be the other method and it is just as complex i would estimate.

iphone SDK: Arbitrary tableview row reordering with core data

What is the best way to implement arbitrary row reordering in a tableview that uses core data? The approach that seems obvious to me is to add a rowOrder attribute of type Int16 to the entity that is being reordered in the tableview and manually iterate through the entity updating the rowOrder attributes of all the rows whenever the user finishes reordering.
That is an incredibly inelegant solution though. I'm hoping there is a better approach that doesn't require possibly hundreds of updates whenever the user reorders things.
If the ordering is something that the data model should modal and store, then the ordering should be part of the entity graph anyway.
A good, lightweight solution is to create an Order entity that has a one-to-one relationship to the actual entity being ordered. To make updating easy, create a linked-list like structure of the objects. Something like this:
Order{
order:int;
orderedObject<--(required,nullify)-->OrderObject.order
previous<--(optional,nullify)-->Order.next;
next<--(optional,nullify)-->Order.previous;
}
If you create a custom subclass, you can provide an insert method that inserts a new object in the chain and then sends a message down the next relationships and tells each object to increment its order by one then the message to its next. A delete method does the opposite. That makes the ordering integral to the model and nicely encapsulated. It's easy to make a base class for this so you can reuse it as needed.
The big advantage is that it only requires the small Order objects to be in alive in memory.
Edit:
Of course, you can extend this with another linked object to provide section information. Just relate that entity to the Order entity then provide the order number as the one in the section.
There is no better way and that is the accepted solution. Core Data does not have row ordering internally so you need to do it yourself. However it is really not a lot of code.

Does Core Data take appropriate action automatically when there's a Low Memory Warning?

Does it turn some managed objects into faults when there's a Low Memory Warning? Or must we do that manually by calling the -refreshObjects:mergeChanges: method which puts the affected managed objects on diet quickly? And...would that actually hurt? What if these objects are currently used by an NSFetchedResultsController to show up on a table view?
Yes, Core Data listens for low memory warnings and will drop its cache and attempt to fault any object that it can to reduce its memory consumption.
If the object is currently being used it would automatically realize that object the next time you accessed on of its properties so from your application's point of view, nothing has happened and no NSManagedObject entities have been released.
In the case where you have established a relationship between two objects, you MUST use the managedObjectContext method -refreshObject:mergeChanges:NO after doing a save, to tell CoreData that the related object can be released. I think of it as the moral equivalent to a [object release]; statement; you are telling CoreData to release the object it read it from the DB. The tradeoff, as Marcus says, is that it will be released from memory cache, but have to be read back in by core data. the good news is that it happens behind the scenes and you don't have to program for it; the bad news is that it happens behind the scenes and thus can 'magically' chew up memory.
Ways around it are many; for example, use a predicate to only select the rows you absolutely must need; don't do a general call to fetch everything and then go through the list one by one. More than likely you will crash when you do the general call and CoreData attempts to load all objects.