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.
You could move your Core Data support code into a class other than your app delegate. Make it a singleton, if you'd like.
You could pass in your managed object context as a property of each view controller.
If you have an
NSManagedObject
that you're already passing around, you can get theNSManagedObjectContext
from themanagedObjectContext
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
Changing Tab Bar Item Image and Text Color iOS
Expanding and Collapsing Uitableviewcells with Datepicker
iOS Is It a Static or a Dynamic Framework
Formatting Phone Number in Swift
Delete Specified File from Document Directory
Creating a Percentage Based iOS Layout
How to Use the Uisearchbar and Uisearchdisplaycontroller
iOS Nsattributedstring on Uibutton
What Is a Provisioning Profile Used for When Developing iPhone Applications
Keeping the Contentoffset in a Uicollectionview While Rotating Interface Orientation
Face Recognition on the Iphone
Pods-Resources.Sh Permission Denied in iOS Project
Disable Bitcode for Project and Cocoapods Dependencies with Xcode 7
How to Change the Color of an Uiimage
Weird Behaviour of Xcode 11 Debugger - Showing Values as Nil When There's a Value
How to Do a Native "Pulse Effect" Animation on a Uibutton - iOS
How to Draw a Single Point Line in iOS
In Swiftui, How to Use Uihostingcontroller Inside an Uiview or as an Uiview