How to Get Managedobjectcontext for Viewcontroller Other Than Getting It from Appdelegate

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

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.

Ways to get your managedObjectContext to the initial viewController OS X

Apple sample code stores and creates the Core Data stack in the app delegate, but that doesn't mean it's right. In fact, it's entirely wrong. The app delegate shouldn't own the data store. Apple does it, and most people follow along, because it's convenient. You should really have a custom class which owns and creates the Core Data stack.

This custom class needs to be instantiated somewhere, that could be in the app delegate and then passed to your root view controller, or it could be in the root view controller itself. This custom class could also be a singleton (because you don't want multiple instances of it).

Each view controller should have a public property for the MOC which is set by its creator (part of the segue code). In this way each controller isn't going and getting the MOC, the MOC dependency is being injected. This helps keep the relationships clean and aids in unit testing. You also don't need the let to check you got the MOC back - if it isn't there it's a development issue (and if it couldn't be created you should never have created / pushed the view controller...).

How to pass a managedObjectContext from the appDelegate to the first ViewController when their is a navigation controller between the two views

If you create a new application from the "Master-Detail" app template in Xcode 4.3 (and 4.2 as well, I think), with the "Use Storyboard" and "Use Core Data" options checked, you'll find the following in AppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
MasterViewController *controller = (MasterViewController *)navigationController.topViewController;
controller.managedObjectContext = self.managedObjectContext;
return YES;
}

This seems to be exactly what you're looking for. The key bit is that you can query a navigation controller for its view controllers.

Setting managedObjectContext in AppDelegate

You can access the managed object context all over the app using the following line of code.

let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext

So you can remove the following line.

controller.managedObjectContext = self.managedObjectContext

Getting managedObjectContext from AppDelegate

First: your property has no attributes. AFAIK, "atomic" is the default then, which would be bad for most objects.

Second: if you always get the MOC from the app-delegate, why assign it to a property? in your addItem-method, just get your MOC from the delegate instead of the property.

Third: if getting the MOC from the delegate still results in the nil-error, you obviously need to check your methods in the delegate.

(originally posted this as a comment, but since comments don't allow for paragraph-formatting, I reposted it as an answer)

Is there a way to get NSManagedObjectContext without having to calling my AppDelegate every time?

There are a few different options.

  1. You could move your Core Data support code into a class other than your app delegate. Make it a singleton, if you'd like.

  2. You could pass in your managed object context as a property of each view controller.

  3. If you have an NSManagedObject that you're already passing around, you can get the NSManagedObjectContext from the managedObjectContext of said object.

There are some ideas in this article on CIMGF.

accessing Core Data context in ViewController properly in iOS

You are right, Apple doesn't recommend that way for iOS applications.

Creation of CoreData stack is the best solution:

So in your AppDelegate.swift:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?
lazy var coreDataStack = CoreDataStack()

func application(_ application: UIApplication,
didFinishLaunchingWithOptions
launchOptions: [NSObject: Any]?) -> Bool {

let navigationController =
self.window!.rootViewController as! UINavigationController

let viewController =
navigationController.topViewController as! ViewController

viewController.managedContext = coreDataStack.context

return true
}

func applicationDidEnterBackground(_ application: UIApplication) {
coreDataStack.saveContext()
}

func applicationWillTerminate(_ application: UIApplication) {
coreDataStack.saveContext()
}

Then, an example of CoreDataStack.swift (the class that holds all the duties about the MOC):

import CoreData    
class CoreDataStack {

lazy var context: NSManagedObjectContext = {

var managedObjectContext = NSManagedObjectContext(
concurrencyType: .mainQueueConcurrencyType)

managedObjectContext.persistentStoreCoordinator = self.psc
return managedObjectContext
}()

fileprivate lazy var psc: NSPersistentStoreCoordinator = {

let coordinator = NSPersistentStoreCoordinator(
managedObjectModel: self.managedObjectModel)

let url = self.applicationDocumentsDirectory
.appendingPathComponent(self.modelName)

do {
let options =
[NSMigratePersistentStoresAutomaticallyOption : true]

try coordinator.addPersistentStore(
ofType: NSSQLiteStoreType, configurationName: nil, at: url,
options: options)
} catch {
print("Error adding persistent store.")
}

return coordinator
}()

fileprivate lazy var managedObjectModel: NSManagedObjectModel = {

let modelURL = Bundle.main
.url(forResource: self.modelName,
withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelURL)!
}()

fileprivate lazy var applicationDocumentsDirectory: URL = {
let urls = FileManager.default.urls(
for: .documentDirectory, in: .userDomainMask)
return urls[urls.count-1]
}()

func saveContext () {
if context.hasChanges {
do {
try context.save()
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
abort()
}
}
}
}

and finally an example of ViewController.swift:

import CoreData    
class ViewController: UIViewController {

var managedContext: NSManagedObjectContext!
override func viewDidLoad() {

let myEntity = NSEntityDescription.entity(forEntityName: "MyEntityName",
in: managedContext)

...

}

}

Accessing ManagedObjectContext

There are several ways you can share your object context around your app. Note that you'll need to make sure you don't use the context on different threads; you'll have to add some complications if you want to use Core Data in both the background and foreground threads.

Which option you use is up to you:

  • I typically move the Core Data objects (persistent store coordinator, managed object model, contexts for foreground and background threads) into a singleton object, so any class that needs it can get the objects without adding much in the way of upward dependencies.
  • You can use dependency injection to pass the context into your view controllers as you create them in your app delegate, and from those view controllers down into other objects that need them.
  • You can expose the Core Data getters in your app delegate's header; then in your classes you can get the app delegate with [[UIApplication sharedApplication] delegate] and get the CD objects directly from the app delegate (I don't like this approach, but it involves the fewest code changes, probably).


Related Topics



Leave a reply



Submit