Core data: executeFetchRequest vs performFetch - objective-c

I want a thorough list regarding comparison between the two. Things I have known:
executeFetchRequest:
Message sent to MOC
Return an array of managed objects
Goal: fetch objects from persistent store to MOC
With table view: has nothing to do with table view
Frequency: often used in a loop, so could be called many many times
performFetch:
Message sent to FRC
After calling it, use fetchedObjects to return an array of managed objects
With table view: FRC is specifically for keeping managed objects and table view rows in sync, and use performFetch to initialize that process.
Frequency: often only once. Unless fetch request of FRC changes, no need to call performFetch a second time
Please correct me if I am wrong and append the list. Thank you.

About executeFetchRequest:
Message sent to MOC
Yes
Return an array of managed objects
Yes, but you can also change the type of results you want to retrieve. In NSFetchRequest you can set a different result type with:
- (void)setResultType:(NSFetchRequestResultType)type
where NSFetchRequestResultType can be of different types. Taken from Apple doc:
enum {
NSManagedObjectResultType = 0x00,
NSManagedObjectIDResultType = 0x01,
NSDictionaryResultType = 0x02
NSCountResultType = 0x04
};
typedef NSUInteger NSFetchRequestResultType;
Goal: fetch objects from persistent store to MOC
Yes, creating a NSFetchRequest and performing a request, it the same as creating a SELECT statement in SQL. If you also use a NSPredicate it's the same as using SELECT-WHERE statement.
With table view: has nothing to do with table view
Yes, but with retrieved data you can populate a table
Frequency: often used in a loop, so could be called many many times
It depends, on what you want to achieve. It could be within a loop or not. Executing the request within a loop could have impact on performance but I would not be worried on that. Under the hood Core Data maintains a sort of cache mechanism. Every time you perform a request, if data are not in the cache, Core Data executes a round trip on your store (e.g. sql file) and populate the cache with the objects it has retrieved. If you perform the same query, the round trip will not performed again due to the cache mechanism. Anyway, you could avoid to execute a request within the run loop, simply moving that request outside the loop.
About performFetch:
Message sent to FRC
Yes
After calling it, use fetchedObjects to return an array of managed
objects
Yes, but you can also retrieve an object with [_fetchedResultsController objectAtIndexPath:indexPath]; if you are populating a specific cell within a table.
Here I really suggest to read a nice tutorial on NSFetchedResultsController
With table view: FRC is specifically for keeping managed objects and
table view rows in sync, and use performFetch to initialize that
process.
Yes, a NSFetchedResultsController works in combination with a NSManagedObjectContext for you. Furthermore, it enables lazy loading of data. Suppose you have 1000 elements you retrieve and you want to display them in a UITableView. Setting a request for a NSFetchRequest like:
[fetchRequest setFetchBatchSize:20];
and using it with an instance of a NSFetchedResultsController, it allows to load 20 elements at first. Then when you scroll, other 20 elements are loaded, and so on. Without a NSFetchedResultsController you must implement this behavior manually. Refer to the tutorial I provided for further info.
Frequency: often only once. Unless fetch request of FRC changes, no
need to call performFetch a second time
It depends on what you want to achieve. Most of the time you could call it once.
Hope that helps.
Edit
You have to call performFetch explicitly. I like to create a property for NSFetchedResultsController in my header file (.h) like
#property (nonatomic, strong, readonly) NSFetchedResultsController* fetchedResultsController;
and synthesize it in your implementation file (.m) like
#synthesize fetchedResultsController = _fetchedResultsController;
Then always within the .m file override the getter to create an new instance of it:
- (NSFetchedResultsController*)fetchedResultsController
{
// it already exists, so return it
if(_fetchedResultsController) return _fetchedResultsController;
// else create it and return
_fetchedResultsController = // alloc-init here with complete setup
return _fetchedResultsController;
}
Once done, within your class (for example in viewDidLoad method) use it like
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
// Handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}

You are comparing the wrong elements. NSFetchedResultsController uses the NSManagedObjectContext to perform the fetch, and under proper configuration, monitors the changes in the managed object context to verify the status of the fetch properties it is monitoring, but the actual fetches are done by the context. On both cases, NSManagedObjectContext does the fetch. The difference being that, using the NSManagedObjectContext directly, you get an NSArray type of object (the actual runtime class is different than an array you get using [NSArray array]), while NSFetchedResultsController has a different purpose (have a collection of results and monitor changes to the records and entity on its fetch request). In other words, NSFetchedResultsController works using the context, but it works different than just a simple collection of objects.
One observation: you shouldn't be using executeFetchRequest inside a loop, especially calling it "many many times". Each fetch has its performance cost. You can call executeFetchRequest once, and do a loop to check the result.

Related

Core Data multi context importing and faulting

I have a a main NSManagedObjectContext used in a few UIViewControllers to display the data (which is a UITableView with a list of Department)
3 entities one Department with a to-one to Boss with a to-many to Employee (In this case the employee have an NSData (which is an image) attribute with allow external storage).
Since I'm importing images in batches I'm doing it in a background thread which has its own NSManagedObjectContext.
The importing consists in creating the Boss entity and all the Employee and setting up the relationships.
Now my issue is :
if I use a child context of the main context for importing and save, then all the images stay in memory even though both context don't have changes.
if I use a context with no relation to the main context the image aren't staying in memory but the new data isn't showed in the UIViewController (obviously since the main context isn't notified of the changes done by the background context)
So I would like to still have the changes appear without having the images in memory (meaning I would like the Department to know that it has a Boss relationship but without having the images in memory). In short I would like them to be turned into fault as soon as the context is saved.
EDIT : I think the problem is when I save the child context, it merges with the main context and from there the newly inserted images stay in memory :/ and I have no idea how to release them (and no they're not auto released even with memory warning...)
EDIT 2 : I think I fixed it, here's what I did :
I used a child context tied to the main context and I listened to all the NSManagedObjectContextDidSaveNotification and for all the inserted updated I call refreshObject:mergeChanges: on it to turn it into fault.
I registered for all the notifications from every context.
-(void)contextDidSave:(NSNotification*)saveNotification {
NSManagedObjectContext *defaultContext = saveNotification.object;
NSArray *insertedObjects = [saveNotification.userInfo valueForKey:#"inserted"];
if (insertedObject) {
NSLog(#"INSERTED : %#", insertedObjects);
for (NSManagedObject *object in insertedObjects) {
[defaultContext refreshObject:object mergeChanges:NO];
}
}
NSArray *updatedObjects = [saveNotification.userInfo valueForKey:#"updated"];
if (insertedObject) {
NSLog(#"UPDATED : %#", updatedObjects);
for (NSManagedObject *object in updatedObjects) {
[defaultContext refreshObject:object mergeChanges:NO];
}
}
}
You can turn a specific object into a fault using refreshObject:mergeChanges:, passing NO for the mergeChanges argument.
Turning object into a fault (flag is NO) means that strong references to related managed objects (that is, those to which object has a reference) are broken, so you can also use this method to trim a portion of your object graph you want to constrain memory usage.
Documentation here.

Core Data – NSManagedObjects in instance variable update

Say that I perform a fetch of all entity Employee objects by doing: NSArray *employees = [context executeFetchRequest:request error:&error];
Then I set an instance variable in my class by doing: self.allEmployees = employees;
Then later in my app I will do some modifying to my employee objects but not through accessing self.allEmployees. I'm modifying them from another class.
Will my self.allEmployees array be updated to the changes I've made from another class to the employee objects? Or will my self.allEmployees array be in the state that the employee objects were when I first performed the fetch? (I'm suspecting the later)
The array you get back from the fetch request holds references to live managed objects. Unless you change a different fetchLimit or batchSize you get an array with as many fault objects as the query would return objects.
When you access a property of one of these fault objects CoreData retrieves the actual data for all the properties transparently and returns these to you.
A managed objects always has its own most recent value that was last called save on. So if you do a modification on the self.allEmployees you need to call save on the MOC and this will broadcast the changes to all other emoployee objects.
This is also the reason why KVO works on NSManagedObject properties, because they get notfified of all saved changes that affect them.
If you alter the objects you receive from the fetch, and don't copy them, then yes.
Those are all pointers.
So you shouldn't need to do another fetch request.
If you change values of an employee, the pointer won't change.
The only thing that changes is the instance variables, or properties.

NSFetchedResultsController missing some objects

I'm facing a strange issue where an NSFRC fetchedObjects array returning not all the objects it should. To give you some context, my application has several list view controllers, each of them having an NSFRC. I'm updating the list view within the delegate method controllerDidChangeContent. The problem I'm facing is the following: after storing an object in a background MOC and saving it, the controllerDidChangeContent is invoked but the object I just saved in the background thread doesn't show up in the NSFRC. Here is a piece of code that I'm using to check this:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
NSManagedObjectContext *context = controller.managedObjectContext;
NSError *error = nil;
NSArray *array = [context executeFetchRequest:controller.fetchRequest error:&error];
if (nil != array) {
NSUInteger count = MIN(controller.fetchedObjects.count, array.count);
for (NSUInteger index=0; index<count; index++) {
NSManagedObject *a = array[index];
NSManagedObject *b = controller.fetchedObjects[index];
// Here you will see that sometimes the objects don't match
NSLog(#"%d: %# <--> %#", index, [[a body] text], [[b body] text]);
}
}
}
I'm expecting the NSFRC fetchedObjects array to be identical to the array returned by a manual executeFetchRequest (I'm using the NSFRC fetchRequest to manually fetch the data). However, this is not the case. The manual executeFetchRequest returns more object than the NSFRC fetchedObjects. Does anyone know what's going on? I've turned the caching on the NSFRC off but the same behavior is reported.
Thanks!
=== Update ====
Some update on that issue. I think there is a bug in Core Data because I was able to see some inconsistent results from the NSFRC and moreover was able to fix the problem by a workaround involving "touching" the object in question. Here is a scenario that explains what is happening:
Imagine the following Core Data model where:
- There are Cat objects and Master objects.
- A Cat can have one or more Master.
- A Master can have one or more Cat.
- A first NSFRC (let's call it NSFRC_A) is created to fetch all the cats with master named "Master_A". The predicate is { ANY master.name == "Master_A" }.
- A second NSFRC (let's call it NSFRC_B) is created to fetch all the cats with master named "Master_B". The predicate is { ANY master.name == "Master_B" }.
- There is a main managed object context that is used in the UI thread only
- There is a background managed object context created for each background thread, using the same persistent store as the main managed object context.
A cat named "Cat_A" is created in the background and assigned to master "Master_A". After the background context is saved, the main context is updated appropriately. At this point, the NSFRC_A notifies its delegate that a change has occurred and correctly reports "Cat_A".
Later on, in a background thread, the same cat "Cat_A" is assigned master "Master_B". After the background context is saved, the main context is updated appropriately. At this point, the NSFRC_A notifies its delegate of that change and correctly reports "Cat_A". NSFRC_B also notifies its delegate of that change but doesn't report "Cat_A" (it is missing from its fetchedObjects). However, if I manually perform a fetch using the same fetchRequest as NSFRC_B, I can see "Cat_A" being returned. The weird thing is that the "Cat_A" instance being returned is marked as a fault which explains why NSFRC_B doesn't return the "Cat_A" because it doesn't see it in memory.
This is a bug because I can fix that behavior by simply logging the "Cat_A" relationship to master when the changes from the background thread are merged into the main context: the logging basically touches the object and forces it to be realized into memory.
The problem appears to be a limitation of the NSFRC. According to this thread on the Apple Forum (https://devforums.apple.com/message/765374): "The limitation being that a fetched results controller for entity A won't always catch an update to entity B that would cause the predicate to change.". To solve the issue, I had to dirty the object I'm looking for before it is being merged into the main thread: then the NSFRC detects that change.

problem with saving data at coredata?

In my application there is searchBar. when we input a text, it will do functionGrab (grab data from internet and save it to coredata), example :
if we input "Hallo"
if([[dict objectForKey:#"Category"] isNotEmpty] && [[[dict objectForKey:#"Category"] objectAtIndex:0] class]!=[NSNull class]){
NSMutableArray * DownloadedTags =[dict objectForKey:#"Category"];
NSMutableSet * TagsReturn=[NSMutableSet set];
for(int i=0;i<[DownloadedTags count];i++){
NSString * Value=[DownloadedTags objectAtIndex:i];
Tag * thisTag= (Tag*)[GrabClass getObjectWithStringOfValue:Value fromTable:#"Tag" withAttribut:#"Name"];
[TagsReturn addObject:thisTag];
}
NSMutableSet * manyManagedObjects = [BusinessToSave mutableSetValueForKey:#"Tags"];
[self removeDifferenceBetween2MutableManagedObjectSets:manyManagedObjects withDownloadedVersion:TagsReturn];
}
So each biz has many categories. WHat happen in multi threaded application is one thread put category. The other thread also put the same category before committing.
So, [GrabClass getObjectWithStringOfValue:Value fromTable:#"Tag" withAttribut:#"Name"]; gives a new object even though some other thread already created the same object without knowing it.
If I synchronized the whole thing that the code would run serially and that'll be slow.
functionGrab:"H"
functionGrab:"Ha"
functionGrab:"Hal"
functionGrab:"Hall"
functionGrab:"Hallo"
something like,it do that functionGrab 5 times
I want to make functionGrab at background, but the problem is when I do that function without synchronized it will save more than one of data, so the result is there are 5 hallo words in my coredata, but if I do that with synchronized, it spent so much time, so slow..
is there any way to help my problem?
I do not recommended having more than one thread "creating" the same types of data for the exact reason you are running into.
I would suggest you queue all of your "creates" into a single thread and a single NSManagedObjectContext to avoid merge or duplication issues.
The other option would be to make the app Lion only and use the parent/child NSManagedObjectContext design and then your children will be more "aware" of each other.

NSManagedObjectContext returns YES for hasChanges when there are none

I created a separate NSManagedObjectContext on a separate thread to perform some store maintenance. However, I have noticed that the context returns YES for hasChanges as soon as a managed object in it is even referenced e.g.
NSString *name = managedObject.name;
This context is created and used exclusively in 1 method. Why is it returning has changes, when there there are none?
That ks difficult to answer without seeing the code. Perhaps your object has a -awakeFromFetch call that touches a property or something else. Normally there should be no changes from just fetching an object unless you are doing something to that object either in the awakeFromFetch or somewhere else in your code.
update
Before the save, grab the deleted array, updated array and inserted array and take a peek at them. That will give you a hint as to what is going on.