CoreData - trouble accessing relationships - objective-c

My data model looks like this:
Object A <----->> Object B <-----> Object C
I fetch a group of Object A's from Core Data via an NSFetchedResultsController. For one particular object in this group, I know that it has only one Object B related to it and I want to retrieve the Object C.
I'm trying to do that like this:
NSArray *bArray = [objectA.relationA allObjects];
ObjectB *myB = bArray[0];
ObjectC *myC = myB.relationB;
(I've also tried [myB valueForKey:#"relationB"] with the same result)
The problem is that I can't get the fault to fire for Object C - I keep getting this for myC:
$6 = 0x0a947c00 (entity: ObjectC; id: 0xa9680b0 ; data: )
I'm passing this value on to another view controller and it's still a fault when it's accessed there, which isn't terribly useful.
It seems silly to have to refetch when I have the object, but I don't know what else to do. All the threads I can find on this say that faults are normal and that they will be fired when you access the faulted object, but that doesn't seem to be happening here.
Any suggestions?
Update: I tried adding this:
[fetchRequest setRelationshipKeyPathsForPrefetching:#[#"relationA.relationB"]];
But it did not make any difference.

The problem is that I can't get the fault to fire for Object C - I
keep getting this for myC:
$6 = 0x0a947c00 (entity: ObjectC; id: 0xa9680b0 ; data: )
You haven't tried to access myC yet. myC will remain a fault until you use it somehow. From the docs:
Fault handling is transparent—you do not have to execute a fetch to
realize a fault. If at some stage a persistent property of a fault
object is accessed, then Core Data automatically retrieves the data
for the object and initializes the object (see NSManagedObject Class
Reference for a list of methods that do not cause faults to fire).
This process is commonly referred to as firing the fault.
So, (assuming ObjectC has a name property) if you do something like:
NSString *name = myC.name;
you should find that the fault at myC fires and you'll automagically have a real object to work with.
All the threads I can find on this say that faults are normal and that
they will be fired when you access the faulted object, but that
doesn't seem to be happening here.
They're right. Unless there's more that you haven't told us, it sounds like you're just expecting the fault to fire at a different time, i.e. when you assign the object to myC. But again, the fault won't fire until you do something to the fault, like getting or setting a property.

Related

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.

Core Data Fault

I am mapping Json Data from Server using Restkit and I am Displaying those data by fetching from db.
There is a refresh button in my view which performs the above operation again.
Scenario:
I have two tables Key & Profile which has one-one relationship. I am fetching data from DB using follwing code
NSFetchRequest *fetchRequest = [Key fetchRequest];
[fetchRequest setRelationshipKeyPathsForPrefetching:[NSArray arrayWithObject:#"Profile"]];
[fetchRequest setIncludesSubentities:YES];
NSArray *sortedObjects = [Key executeFetchRequest:fetchRequest];
Above array returns all object in DB. but when i check that using breakpoint, i got some core data fault which was the reason for not showing all the data.
//All data in sortedObjects are like this.
<Key: 0x889f2f0> (entity: Key; id: 0x889e400 <x-coredata://981A476D-55AC-4CB4-BBD8-E0285E522412/Key/p1489> ; data: <fault>)
Any idea
This might be a misunderstanding about what a 'fault' is.
As described in Apple's documentation:
Faulting is a mechanism Core Data employs to reduce your application’s memory usage.
and
A fault is a placeholder object that represents a managed object that has not yet been fully realized, or a collection object that represents a relationship:
So, if you see the word 'fault' in logs when you're working with Core Data, it doesn't mean that an error has occurred. What unexpected behaviour are you seeing in your app?
You haven't actually described a problem. A Core Data fault isn't an error-- it's more like a page fault in a file system. It just means that the data hasn't been read yet. What you're describing is completely normal and expected. If you access any of the attributes of the returned objects, the fault will be automatically filled and you'll see the results. So if your Key entity has an attribute called name, you can still look up the value(s) for name and even log them if you want.
You could force the faults to be filled by adding this before executing the fetch request:
[fetchRequest setReturnsObjectsAsFaults:NO];
It's not necessary though and, depending on what attributes you have and how many objects you fetch, could use a lot more memory than is really needed.

How can I create a Core Data relationship from one entity to another, existing entity?

During the creation of a Core Data entity (Event), I am creating a relationship to another entity (Team). This relationship is many-to-one from Team to Events (one team, many events) and has an inverse relationship from Event to Team.
Team<----->>Event.
The delete rule for both relationships is set to 'Nullify'.
The below block of code works successfully on first population when a new Team is created during the creation of each Event. However, if I then remove an Event and attempt to re-add it, the existing Team is retrieved but the code fails when attempting to add the Team object to the Event in the final line of the example. The error is as follows: -[__NSCFDictionary managedObjectContext]: unrecognized selector sent to instance 0x699ed60
What is the correct way to create a relationship between the Event object to the Team object that already exists?
Team *currentTeam = self.team;
Team *newTeam = (Team *)[self loadTeamForNid:[NSNumber numberWithInteger: [teamNid integerValue]]];
// If the nid of the referenced team has changed,
if (![[[currentTeam nid] stringValue] isEqualToString:teamNid]) {
currentTeam = nil;
currentTeam = newTeam;
}
// If an event has not been set by this point, it does not exist in the CD store, and we need to create it.
if (currentTeam == nil) {
currentTeam = (Team *)[NSEntityDescription insertNewObjectForEntityForName:#"Team" inManagedObjectContext:[delegate managedObjectContext]];
[currentTeam populateTeamWithNode:[node nodeGet:teamNid]];
}
// TODO: This breaks on reload of an object
// self.team = currentTeam;
[self setValue:currentTeam forKey:#"team"];
Conceptually, you aren't mistaken: you set the event's "team" property to an instance of NSManagedObject that represents the appropriate team.
This message:
-[__NSCFDictionary managedObjectContext]: unrecognized selector sent to instance 0x699ed60
Means that some line of code is handling an instance of NSDictionary where it expects (I assume) an instance of NSManagedObject. When it tries to query the object's managedObjectContext, an exception is thrown, because an NSDictionary doesn't implement a method for that selector.
The first thing to do is put a breakpoint on that last line and see if currentTeam is actually an NSDictionary in disguise. (This seems unlikely, given the code above an exception would have been hit earlier.) If not, you'll have to hunt around for related properties that might be involved in this code path.
Note that Core Data supports a fetch request style where it returns NSDictionary instances instead of NSManagedObjects; if you are using this anywhere in your code, you might be accidentally passing the result along to another method that doesn't expect it.

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.