I am using the core data stack shown in the image below. I want to design a structure where objects can be created in both worker contexts.
What I am observing in the setup is if both contexts try to create the same object (for a unique key) at around the same time, db ends up in creating two rows for the table. Is there a way to solve this? Thanks in advance for your response.
The only way you can ensure uniqueness would be to have a coordinating object that all contexts turn to to verify their operation (a "uniqueness enforcer" if you will).
The general algorithm is described HERE, however you fall under the "multi-threaded/context" category and this will complicate things.
In a multi-threaded environment, your enforcer would have to perform a save to the store (using its own managed object context) before returning results to the calling object.
The general flow would be (no cache version):
A context request object for keys from the enforcer
The enforcer issue the request "under lock" (either locking an actual lock or using a serial dispatch queue)
the enforcer query the store for existing objects
create objects for missing keys and save them
you might want to mark the objects as stubs, as the caller might not eventually save and it will give you a flag to ignore them in your fetch requests in your views
build the results array with the objects he created
the results might be NSManagedObjectIDs or imported objects in the caller context otherwise you risk cross context access of managed objects
Related
I have a model some instances of which I need to persist. Only some, not all, since persisting all instances would be wasteful. The model has primaryKey of type Int
I need to be able to pass all objects from background to main thread since Realm objects can only be used by the thread on which they were created. Current version of Realm Swift (0.94) does not seem to support handing the object over to another thread directly.
For persisted objects (the ones saved to storage with write) this is not a problem, I can fetch the object on another thread by primaryKey.
Unpersisted objects, however, are problematic. When I create a new object with the same primaryKey in background (I suppose it should be treated as the same object since it has the same primaryKey) and try to fetch it on the main thread (without persisting changes with write since I don't want in in the storage), I seem to get the old object.
I see the following solutions to this problem:
1) persist all objects (which is undesirable and otherwise unnecessary)
2) keep separate model for the objects I want to persist (leads to code duplication).
3) use init(value: AnyObject) initializer which creates unpersisted object from a Dictionary<String, AnyObject> (would probably require manual copying of object properties to dictionary, tedious and potentially error-prone)
Are there any better solutions?
Offtopic: I haven't tried Realm for Android, is situation any better with it?
You are free to pass unpersisted objects between threads as you wish -- only persisted objects cannot yet be handed over between different threads.
I think your problem is that you are creating two objects that you want to be the same object and there is no way the system can know which one you want.
The solution is as simple as it is generic: create a new object only after checking that its unique attribute does not exist already. This should work equally well with persistent and non persistent objects. Obviously, you need to have a central, thread safe in-memory repository where you can go and create new objects.
You write:
I seem to get the old object.
There should not be any old object if you have checked before.
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.
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.
I have what I think is a simple task.
I have a method called [self getPerson] that makes a simple GET request from a web service for a Person that returns some JSON and then transforms the JSON into an NSManagedObject. checks for an existing identical Person NSManagedObject, and if none is found, saves the Person into core data. No problem.
However, If I fire off this method twice in a row, I get two Person NSMangedObjects persisted into Core Data. For example:
[self getPerson];
[self getPerson]; ---> yields duplicate `Person` objects saved in core data, no good.
How can I ensure that only one Person object is saved in Core Data (no duplicates allowed)?
I know the issue, I just don't know how to fix it. The issue is that I need a transaction. When [self getPerson] fires the first time, the method checks for an already existing identical Person object, finds none, and saves a new Person into core data. This is correct. When I fire [self getPerson] the second time, the method checks for an already existing Person object, doesn't see one, and is then persisting another Person object. This is not correct. I would like to make it so that the second time, and third time, and fourth time, to the 1000th time, checking for an existing Person object will only occur after the managedObjectContext saveoperation is done. Right now the check for an existing object is happening so fast (before the save is done).
Do I need a serial queue? If so, should this be a dispatch_async or dispatch_sync? I've even toyed with the idea of trying to use a performSelectorWithDelay trick.
Once you create the object it will exist in the database regardless of you calling save. So you should not create a managed object if one exists already. It's not entirely clear what your code logic is but from your description you say you transform the JSON to a managed object and then you check for an identical existing one and if none is found you save. Well when you create the managed object you have created it, so it's too late to check if an identical one exists. Saving does not create the object it just saves it to the store if it hasn't already been saved.
So first check if an person object exists with the attributes in the JSON and if not then create a managed object.
Well, in this case a serial queue will ensure that operations are performed in the correct manner.
From you question, maybe I'm missing something, I cannot understand if the getPerson method is responsible to both get and save data. If not, you should do it.
Anyway, if you use JSON and the person you retrieve form the server has a unique identifier, you should use that to query against Core Data and verify if it exists or not. The correct manner to do it is to implement Implementing Find-or-Create Efficiently.
A simple question. Is the any reason for calling the getPerson twice? Could you not prevent it using a flag (or a transient property)? Just simple ideas.
In my code I do a large import of thousands of objects and execute save after each object .
Do I need to turn each object into faults after saving it to save memory or will Core Data turn it automatically into faults? Unfortunately, I have not found any clue in Apple`s doc.
Thank you!
A. You can turn an object into a fault with refreshObject:mergeChanges: (NSManagedObject). This will give up strong references to related objects, so that they can be released. (If they are not holded by another reference.)
You can turn a realized object into a fault with the
refreshObject:mergeChanges: method. If you pass NO as the mergeChanges
argument, you must be sure that there are no changes to that object’s
relationships. If there are, and you then save the context, you will
introduce referential integrity problems to the persistent store.
(Link)
B. You can wipe all objects out with -reset (NSManagedObjectContext) as Daniel G said. But this really wipes out the objects, references can break.
C. I think, that there is no promise of CD to turn all saved objects into faults. (Unsaved objects cannot turn into faults.) But simply overwrite -didTurnIntoFault (NSManagedObject) to see the behavior of CD.
I'm not exactly sure what you are asking but if you have a lot of objects hanging around in your context you can always use the method -[NSManagedObjectContext reset] to reset the context and purge memory. You also don't want to do this if you have any objects that reference NSManagedObjects within the recently reset context.
I'm not sure as to the nature of your application or why you would need to execute a save on thousands of objects, it seems that you should look into using batches for your fetch requests. This way core data will guarantee that only a specified number of objects will be living in the context at any given time.
I hope this helps?