How to get managedObjectContext for viewController other than getting it from appDelegate?
Asked Answered
C

2

21

Recently I came to know that "You really shouldn't be calling down to the AppDelegate to get the managed object context". Apple also has put this recommendation into their documentation here. It goes like this :

A view controller typically shouldn’t retrieve the context from a global object such as the application delegate—this makes the application architecture rigid. Neither should a view controller create a context for its own use (unless it’s a nested context). This may mean that operations performed using the controller’s context aren’t registered with other contexts, so different view controllers will have different perspectives on the data.

Further they have mentioned some other ways to get context. So far I am unable to figure out what they are trying to say there. Can anybody please put some light on the issue. Any code snippet supporting statements would be most welcome.

EDIT

Sometimes, though, it’s easier or more appropriate to retrieve the context from somewhere other than application or the document, or the view controller. Several objects you might use in a Core Data-based application keep a reference to a managed object context. A managed object itself has a reference to its own context, as do the various controller objects that support Core Data such as array and object controllers (NSArrayController and NSObjectController in OS X, and NSFetchedResultsController in iOS).

Retrieving the context from one of these objects has the advantage that if you re-architect your application, for example to make use of multiple contexts, your code is likely to remain valid. For example, if you have a managed object, and you want to create a new managed object that will be related to it, you can ask original object for its managed object context and create the new object using that. This will ensure that the new object you create is in the same context as the original.

What exactly it is? Am sure its not similar with the Highly voted answer below. Can somebody help me to understand this portion of Apple documents?

Communicant answered 10/1, 2014 at 17:16 Comment(7)
Definitely don't use the app delegate. If you have to use a global object, put it in a singleton whose name reflects that it contains this database.Gatefold
@Gatefold I would love to know, why not appDelegate & why another singleton is ok?Communicant
Because the app delegate is named "app delegate". It is intended for doing things that pertain to the entire application and how it is perceived from the outside (dock menu, opening files etc.). It is code specific to this one application and by design not reusable. Other singletons are independent from this particular application. Also, your singleton has a more precise name. If you put stuff like that in the app delegate, it will collect crap, because everything that is a singleton is "for the whole application".Gatefold
The recommendation against using the app delegate is probably because if you ever need to introduce a new MOC then the setup allows for more "modular" code. Its more complaint to OO architecture.Hickson
"The downside of passing the same context between controllers is that if a same entity is modified in two different places, you have to manage the merge conflict." is usually not a problem in iOS since you normally see one view controller at a timeHickson
@WalterMartinVargas-Pena It is not a problem in ANY operating system as there is no existing operating system that allows a user to input data in two places at the exact same time.Kellene
My rationale for being against using the app delegate is a little too complex for a comment, so I blogged about it: orangejuiceliberationfront.com/…Gatefold
K
40

It is called dependency injection. Basically the caller/constructor should be setting the NSManagedObjectContext onto the called/constructed.

In your AppDelegate you should set the NSManagedObjectContext into the rootViewController that is associated with the UIWindow.

Your rootViewController should then set the NSManagedObjectContext into the next view controller and so on.

How? It is just a simple proper on the view controller class and the caller uses:

[nextViewController setManagedObjectContext:[self managedObjectContext]];

Some others may recommend a singleton but that is another deep dark pit that is best avoided.

Update

Dependency Injection is the best approach.

It is the approach Apple has designed around. The other choice involves some form of a singleton: AppDelegate or another one.

"The downside of passing the same context between controllers is that if a same entity is modified in two different places, you have to manage the merge conflict."

That is a completely different problem and it is not going to be solved with multiple NSManagedObjectContext instances. In fact, multiple instances will make the situation worse and guarantee a merge conflict.

In that situation, your view controllers should be listening for changes in the managed object and reacting to them. Making it impossible to update it in two places at once in the UI. The user simply cannot focus on two places at once and therefore the second location will be updated in real time.

That is the right answer for that problem.

Having both entities in the same context will make sure that works correctly. Multiple contexts will cause their to be two objects in memory with the same data and no way to notice the changes without a save to the context.

However, if you are having view controllers that are modifying data without user intervention then you have a separate problem. View controllers are for the user to modify or view data. They are not the place for any kind of background processing of the data.

If you are in an import situation then that is a different question than the one you asked. In that case you are (should be) using multiple threads (UI thread, import thread) and you must have at least one context for each.

In that situation you do risk a merge conflict and you need to code for the situation happening. First step is to change the merge policy on the NSManagedObjectContext instances.

Update

I suspect you are misreading that documentation.

What that is describing is the ability to get the NSManagedObjectContext out of the NSManagedObject instance. This is absolutely useful. Take for example a view controller that has the ability to add or edit an object. By pushing just the NSManagedObject to the view controller you can control and decide what that view controller is going to be touching. The receiving view controller knows that it needs to allow editing of the received NSManagedObject. It does not care what NSManagedObjectContext it is working with. It could be working with the main, it could be working with a child, it could be in isolation in a unit test, it doesn't need to know or care. It simply displays the data from the NSManagedObject it is handed and saves the associated NSManagedObjectContext if the user chooses to save the edits.

That documentation is NOT suggesting having some universal location for your NSManagedObjectContext to live (aka a singleton). It is suggesting that if you have another way to access the NSManagedObjectContext that is associated with a NSManagedObject that it is ok to do so and definitely makes sense to do so.

Kellene answered 10/1, 2014 at 17:41 Comment(4)
I know this approach, but getting mixed response for this. there is this statement "The downside of passing the same context between controllers is that if a same entity is modified in two different places, you have to manage the merge conflict." Somewhere I read that method mentioned in question is better than method you mentioned. I am trying to look back at that article. So, not sure if your method is really a good approach. Hoping for a better explanation & alternative. 1 up for the alternative, but not convinced.. sorryCommunicant
Please see Edit section of my question. Is it suggesting other better way of doing it?Communicant
Awesome…. Thank you so much. You gave me clear picture here.. Accepted :)Communicant
I've been struggling with this for the longest time, thank you!Hawsehole
E
0

The singleton approach has worked best for me when it comes to grabbing my Managed Object Context. It really depends on the complexity of your app, but in my case I typically keep one Managed Object Context and work with temporary nested contexts when I need to make changes.

By using a singleton-based "DataManager" class that contains all the Core Data initialization methods with public references to the the Managed Object Model and Context I am able to get to the data by importing my "DataManager.h" class and making calls to the singleton:

// I have a method to create an object that requires the Manage Object Context, so I call it from the DataManager singleton
SomeObject *newObject = [SomeObject createObjectInContext:[[DataManager sharedInstance] managedObjectContext]];

// I have a method in DataManager to save the context
[[DataManager sharedInstance] saveContext];

That is actually the simplified version. I typically use nested Managed Object Contexts so that my main Managed Object Context is not modified until that addition or modification of a Managed Object is confirmed by the user. That complexity can all be contained in the "DataManager" class.

This is slightly off-topic, but in case you need to learn more about nested contexts: I had serious issues when NOT using nested contexts to make changes to the main Managed Object Context. This article, while some of it went over my head, helped me understand and implement nested contexts:

http://www.cocoanetics.com/2012/07/multi-context-coredata/

Elephus answered 10/1, 2014 at 19:30 Comment(3)
Putting your Core Data code (stack, migration, etc) into a single handler is a solid suggestion. Making that a singleton is not. With a singleton you cannot do unit tests, cannot directly control which MOC is being pushed into a view controller and other issues.Kellene
I'm using unit tests yet (it's on the things-to-learn list), but with regards to the singleton approach, isn't there only one MOC in singleton that can be called from any given VC? I don't want to side track the main topic in this question, but I'd like to understand your statement about "cannot directly control which MOC is being pushed". There is only one MOC, and if changes are needed, I create a temporary nested MOC and either save changes back up to the main MOC or discard the temporary MOC. I've never had problems with this approach, but it doesn't mean they aren't there...Elephus
Take the example of an insert/edit view. On an edit, I can push a MO from the main MOC. On an insert I can create a new MOC, create an MO associated with it and send that MO to the VC. The VC doesn't need to care. For tests, I can mock out the MOC and test the interface to my VCs/network layer, etc. and get known answers. A singleton makes all of that harder for the single purpose of avoiding dependency injection. Singletons eventually paint you into unnecessary corners and are really a hack that should be avoided. Google for "singletons are evil" and read :)Kellene

© 2022 - 2024 — McMap. All rights reserved.