Refetching object when traversing relationship in Core Data - ios

In our app, we make use of multiple NSManagedObjectContext instances, one per thread created. So when I'm about to use any object, I always make sure that I got the object from the correct context by doing this.
object = (ObjectClass *)[[contextProvider contextForCurrentThread] objectWithID:[user objectID]];
Since I want to make sure the object is updated I go and refresh it:
[[contextProvider contextForCurrentThread] refreshObject:object mergeChanges:YES];
And my question is: If I want to traverse relationships from this object, i.e object.someRelationship, Should I also make sure that the relationship is in the current context, and refresh it? What would be the correct approach?. Refetching and refreshing every time is such a pain...

Look into nested contexts and concurrency types. They won't solve your underlying problem but they are much better than what you already have.
You need policies. To me it seems that your software (without knowing it at all) is lacking structure. Structure comes with policies.
Even if you were to refresh your objects (all of them) all the time you have serious race conditions.
Without knowing the details of your app nobody will be able to suggest anything except "think about what you do" and "introduce policies".

Related

NSManagedObjectContext refreshObject causes duplicates in NSFetchedResultsController

I have an issue where if I make changes to a core data object, save then refreshing the object, causes my NSFetchedResultsController to show a duplicate object. I think I understand what's going on, but I'm looking for someone to confirm, and also to hopefully give some more detail as to why.
To explain in more dtail I have two entities, Fixture and Position. A Fixture has many Positions, and a Position belongs to only one Fixture. To reproduce the issue I do the following:
Fetch all Positions.
Modify some value (any one) on that objects Fixture. I.E foo.fixture.name = "foobar"
Save the context
Refresh objects by calling context.refreshAllObjects, or context.refreshObject(foo, mergeChanges: false/true).
I have a tableview using a fetched results controller which displays Fixures. After doing the above the tableview will display duplicates for each item (it doesn't matter if I use the delegate methods of the FRC to do the update or I just reload the tableview).
It appears what's happening is that the refresh invalidates the objects that the FRC knows about, while at the same time gets knowledge of another set of objects. If, as step #5, I call frc.performFetch() then the problem goes away.
Other things to note:
No matter how many times I run the code I only get two of each object (I'm using a random button to trigger it for testing).
init(entityName, context) is called on my Fixture subclass as soon as I access the Fixture property of my object during the next code run (i.e after refresh was called).
In my sample everything is taking place on the same context (though it happens with child contexts as well)
To give some more context as to how I got myself in this situation in the first place users can click on a fixture in the list and then ultimately narrow down on a single position a few screens later where they can perform actions that modify the fixture. There are other active areas of the application at this point that are listening to the NSManagedObjectContextDidSaveNotification and I want them to update their objects so they can display the correct data, which is why I was calling refresh.
I've dug around in the docs and I can't see anything that specifically explains my theory that updating the context causes NSFetchedResultsController to have invalid objects. Can anyone shed some light on this behavior?
First, you really should not be overriding -init... on a NSManagedObject. That is one of the methods that you are strongly discouraged from overriding and can very easily be the source of your issues.
Second, your entire UI should be using a single instance of the NSManagedObjectContext that is associated to the main queue and therefore there should only be ONE instance of any particular entity in your UI. If you have multiple contexts you are just making things more complicated for yourself. If you are using a single context keep in mind that no matter how many times you fetch an object against that context you will get the exact same pointer back.
The NSFetchedResultsController will never create objects, it only fetches and provides them for display. Therefore the NSFetchedResultsController is only reporting the fact that you have created this duplication somewhere else in your code.
Now some questions, do these duplicates get pushed down to disk?
Can you see them in the store file on disk?
When you re-launch your application are the duplicates still there?
When you put a print in your custom -init methods on the NSManagedObject; do they fire more than once?

Would this fill the child context with only TemporaryIDs?

I was wondering if someone would be able to answer this question. I am currently building my application using the CoreData stack as described by Marcus Zarra in his blog http://martiancraft.com/blog/2015/03/core-data-stack/. He describes the usage of managedObjectContext as the Single Source Of Truth where all insertions/updates/deletions should be done on this context. No exceptions. Curiously then, since this context is a child context to the private context, if I keep inserting new NSManagedObjects into managedObjectContext... wouldn't this context be filled with temporaryObjectIDs since the parent does not refresh the child context? I ask this question because how would I retrieve this NSManagedObject back from the PSC using the NSManagedObjectID if I only have the temporary one? Would I have to explicitly throw out another performBlock using the privateContext to fetch for it? That feels like a very inelegant of a solution.
You can force the conversion to happen early, before you've saved changes, like so:
[moc obtainPermanentIDsForObjects:moc.insertedObjects.allObjects error:&error];
As I understand it, the object is already in both contexts. You don't need to retrieve it from the store, as it's cached.
You still have the object and could query the store, based on the object's attributes. The only need for the ID was if you needed to pass it to another thread, but you only have the one Single Source Of Truth. What other thread is there?
Update:
After discussing your app, I would suggest not to add complexity if there is no need for complexity. You already have an API thread. Don't try to prematurely optimize it by splitting an API task into multithreaded sub tasks. You're adding overhead and the unnecessary burden of passing IDs between private contexts.
Treat what the background API is doing as a single operation, and let it download, insert, save, then notify the SSoT, which can then (re)fetch.
If it is responsive enough, you're done. You have a simple, contained approach, with no need to pass IDs.
In general, don't optimize unless there's a need for it.

Core Data--pass name of entity or reference to entire entity?

I'm making a simple bank account tracker, for self-instructional purposes. I'm using Core Data to store three entities, related as in the screenshot:
WMMGTransaction objects are simply stored as they are recorded, and extracted as needed to feed tableviews and detail views. This will be done via NSFetchedResultsController and a predicate. I'm using MagicalRecord to access Core Data, if that matters.
My question is this:
When I pass WMMGAccount data from one VC to another, such as when creating a new account, or when selecting one from a list (via delegation as a rule), does it matter if I pass a reference to the entire entity, or can I just use an NSString bearing the .name of the account and identify the account when required with a predicate and an NSFetchedResultsController? I guess this is a strategy question, and may generate discussion, rather than having a cut and dried answer, but I'm wrestling with it, so I thought I'd ask.
It sounds like you're asking if you should pass an object to the code that needs it, or if you should pass information that could be used to look up the same object again.
Unless you need to use the managed object on a different thread or queue, you should always pass the actual object. No sense re-fetching an object you already have. It's extra work and code complexity that (unless there are some unusual extenuating details you didn't mention) won't help in any way.
If you are needing to use the object on a different queue or thread, passing information that can be used to look it up is the correct approach. But in that case-- don't pass the value of one of the properties. Use the managed object ID.
Core Data won't force name values to be unique, while the object's managedObjectID is unique. It's also faster when retrieving the object, because you can use objectForID: or existingObjectForID: instead of performing a fetch.

How to write changes from one ManagedObject to similar ManagedObjects

I am new with Core Data and have a problem which sounds trivial to solve (at least thinking in SQL) but I can't get my head around that with Core Data.
What I'm trying to do is the following: I have a fetched ManagedObject, do some changes and save it again. This ManagedObject has an attribute id. I want to write the changes I made to this ManagedObject to all the ManagedObjects with the same id.
I was thinking to overwrite willSave: and fetching the other ManagedObjects with the same id there but this won't work because I would encounter an infinite loop there.
Can somebody give me a hint on how to progress from here? Thanks in advance
You could make willSave work, but it isn't going to be a nice bit of code to ignore all of the invalid triggers.
It's better to have a class which manages this functionality, pass in the new data value and the attribute id and allow it to do the fetch and update all of the fetched objects (and trigger the save).
I would, indeed, try to find some better way to deal with it, because actually you should't think of Core Data as of SQL with its triggers.
But actually you can indeed use willSave method and avoid infinite loop. See
NSManagedObject Class Reference willSave method
If you change property values using primitive accessors, you avoid the possibility of infinite recursion, but Core Data will not notice the change you make.
So basically in your willSave method you'll need to call some fetchRequest to get all instances of the same Entity, then loop through them and update using primitive accessor: setPrimitiveValue:forKey:
Also I would advice to verify objects in loop whether they are removed (-isDeleted) and, probably, whether that object is not your current one (by comparing managedObjectIDs)

db4o object update dilemma

I am new to db4o.
I have this question in mind:
when the object are retrieved from DAL, maybe it will update in Business layer, then we lost it's original property, so when it comes to updating how can I find which one is the original object in the database to update?
You need to be more precise about "the object". If you modify the object instance's properties, simply storing it again will perform an update:
MyClass someInstance = ObjectContainer.Query<MyClass>().FirstOrDefault();
someInstance.Name = "NewName";
someInstance.PhoneNumber = 12132434;
ObjectContainer.Store(someInstance); // This is the update call
[This is just pseudo-code]
So you don't need to match objects to each other as you would have to when using an RDBMS.
However, you need to make sure you are not using a different instance of ObjectContainer, because a different container will not know these objects are the same instance (since there is no ID field in them).
Your application architecture should help to do this for most workflows, so there should be really only one IObjectContainer around. Only if timespans are really long (e.g. you need to store a reference to the object in a different database and process it somehow) it'd use the UUID. AS you already pointed out, that requires to store the ID somewhere else and hence complexifies your architecture.
If you however intend to create a new object and 'overwrite' the old object, things get somewhat more complicated because of other objects that might refer to it. However, this is a somehwat pathological case and should typically be handled within the domain model itself, e.g. by copying object data from one object to another.
You should load the object via its ID:
objectContainer.get().ext().getByID(id);
or via its UUID:
objectContainer.get().ext().getByUUID(uuId);
See the docs for the latter one. For an explanation see the answer here or the docs here. In short use uuid only for long term referencing.

Resources