Core Data - Basic Questions - iphone

I would like to know how the following works in Objective-C
in my header file i have the following which is initialized from a different view controller
#interface UserLookup : UIViewController {
NSManagedObjectContext *referringObject;
}
and in my implementation file i have to pass this NSManagedObjectContext to 2 child view controller then does it make a difference which view controller is called first... and does the NSManagedObjectContext changes in any one of the child controller?
Regards

You don't really need to pass it around to every view controller where you will need Core Data access - just use
NSManagedObjectContext* moc = [(MyAppDelegateClass *)[[UIApplication sharedApplication] delegate] managedObjectContext];
managedObjectContext must be an accessible ivar of your app delegate.
It makes it conceptually similar too. There is one NSManagedObjectContext (in most uncomplicated apps, thought you can have multiples), owned by your app delegate. You don't ever retain or release it (except for when it is created in the app delegate, on first access if you are using Apple's template code, and when it is released in app delegate's dealloc.

ASFAIK it should not make a difference which viewController is used first. Think of the NSManagedObjectContext as a pointer to the physical datasource.
You can add and remove NSManagedObjects from the context. But these changes are only saved to disk when you call the save: method.
Does that help at all?

To use only one context is simple and works fine. But you can also create a new managed object context and pass it to the other view controller. Though the persistent store is only one, but you can have multiple contexts.
Each context have each undo manager, so you can control changes of managed objects in the context. You can save changes in only one context even if the other context has also some changes. After saving a context, you can merge the changes of two contexts by the following NSManagedObjectContext instance methods:
- (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification
- (void)refreshObject:(NSManagedObject *)object mergeChanges:(BOOL)flag
Maybe this document helps you to understand more detail.
http://developer.apple.com/library/ios/#documentation/DataManagement/Conceptual/CoreDataSnippets/Articles/stack.html
And the CoreDataBooks Apple sample code uses the additional context.

Related

MVC-friendly approach to Core Data

Ok so I am pretty new to Core Data, and I am now starting to dig into it more and I have a question about the correct way to implement it.
I have a parentViewController object that is loaded from the appDelegate. The parentView is a view that sets up a paging UIScrollView for three more viewController objects, it's children. The view loads up and I can page between the three views beautifully.
All three of the views need to have a significant amount of data exchange with Core Data. Should I pass the reference of the managedObjectContext into each different view within the parentView so that all the views can pull from and write to Core Data themselves? Or should I keep the reference in the parentView, and let the children pass the requests to the parent to handle?
Thanks
I have typically used a third option, at least for simple applications with a single persistence store that I access throughout the lifetime of the app: Make the managedObjectContext a property of your application delegate, and initialize it when the application is started. Then, you can access it from anywhere in your application using something like:
MyApplicationDelegate *delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = [delegate managedObjectContext];
This approach is not ideal for all designs and architectures, but I'm finding it works well for me and keeps my code relatively clean.
Controllers should never communicate directly with each other in MVC. Any data that needs to be shared between controllers should be shared through the model layer, of which the MOC is a part. So, ideally, you should pass the MOC to every object that needs to use it. Perfectly OK to have multiple objects reading/writing to the same MOC simultaneously; respects principles of Dependency Injection; and probably less code to write.

How do you pass an object between ViewControllers on iOS?

Using the "Tab Bar" template in Xcode 4, each ViewController is created automatically, so I don't have the chance to set any properties on a ViewController as it's created. I want each ViewController to have access to an object (an instance of FMDB's FMDatabase).
There are a number of questions on StackOverflow relating to this already, but some are assuming that you are creating the VC's by hand, and others recommend using the AppDelegate as a mediator:
MyAppDelegateClass *appDelegate = [[UIApplication sharedApplicaton] delegate];
myLocalProperty = appDelegate.someDataModelProperty;
The above seems rather "hacky" to me. Is there a better way to access a single object from multiple ViewControllers?
Most probable implementation of FMDB's Data base would be to implement it as a Singleton and access it through out the application. Is there any reason for not doing so ?
This link should help you in doing so:
How do I make FMDB's database a singleton
To expand on KKK4SO's answer, you could implement the Database as a singleton. Then, have all of your UIViewControllers be a subclassed UIViewController that contains methods to access and modify the Database singleton.
MyAppDelegateClass *appDelegate = [[UIApplication sharedApplicaton] delegate];
myLocalProperty = appDelegate.someDataModelProperty;
There is a spelling error in this code 'sharedApplicaton' is incorrectly spelled it should be 'sharedApplication'.
Shame on me for copy and pasting! Anyway this will work for a quick and dirty way of passing data between ViewControllers etc.

How to share a ManagedObjectContext when using UITabBarController with inner UINavigationControllers

I have an architectural question. My App uses a TabBarController right in the application window. The ApplicationDelegate creates the managedObjectContext, although it actually doesn't need it.
Each ViewController in the TabBarController is a NavigationViewController. The first view controller for each NavigationController are my custom views. All is createde an linked via Interface Builder.
Now, how do I pass the managedObjectContext around the right way? Actually I need my views to load the data as soon as possible so that when the user chooses a tab or navigates through the NavigationControllers, the data is already there.
So my questions are:
How to I pass the context properly?
When should I fetch my data, i.e. in which method? "viewDidLoad" or "viewDidAppear"?
Thanks for all ideas!
You should generally stay away from getting shared objects from the app delegate. It makes it behave too much like a global variable, and that has a whole mess of problems associated with it. And singletons are just fancy global variables, so they should be avoided unless really necessary, too.
I would add a managedObjectContext property to each of your view controllers and assign that when you're creating them. That way, your view controllers don't have a tight linkage with the app delegate.
As for when to fetch the data, you should do it lazily. Core Data is really fast, so I would wait until viewWillAppear: to do your fetching. If you wait until viewDidAppear:, the view is already on the screen and there will be a flicker when the data loads. Do be aware, though, that viewWillAppear: is called every time your view will become visible (e.g. when the user taps the back button on the navigation bar, or a modal view controller is dismissed) so you might want to track whether you've already loaded the data and skip the loading on subsequent calls.
I've ran into this same problem, i'll share my solution.
First you need a reference to the Nav Controller in the Tab Bar in the nib file, make sure you connect it up.
IBOutlet UINavigationController *navigationController;
Then, get the Controller as recommended in the support docs and send it the managedObjectContext:
SavedTableViewController *saved = (SavedTableViewController *)[navigationController topViewController];
saved.managedObjectContext = self.managedObjectContext;
Alex is right, "You should generally stay away from getting shared objects from the app delegate. It makes it behave too much like a global variable, and that has a whole mess of problems associated with it."
You can get it from the app delegate at any time like this:
myApp *d = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = d.managedObjectContext;
Or variations of the above.
Other than that you can add a property to all your viewcontrollers and pass it around or you can create a singleton and reference that globally.
Swift
You should not share a NSManagedObjectContext, but you can share the NSPersistentStoreCoordinator.
Thus, you can create a new managed object context for each view, each sharing the same store. It is the preferred method, and allows concurrent, multithreaded access. In the example below, I am assuming that your AppDelegate, *if created with a recent version of Xcode with Use Core Data checked*, has a property named persistentStoreCoordinator:
lazy var managedObjectContext:NSManagedObjectContext? = {
// This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
let coordinator = appDelegate.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}
}()

What's an effective CoreData context/coordinator strategy for this common application pattern?

I have a common application pattern: user enters data in the main view controller, then views it in a table in a modal view controller, where rows can be deleted or modified. I was following the general design strategy from the Stanford iPhone course, but somewhere things went off the rails and all I've been getting is SIGABRT's and exceptions like "Illegal attempt to establish a relationship 'xyz' between objects in different contexts."
As in the Stanford course, I use a singleton class called "Database" which should return the same context whenever requested. So the first commands in my viewDidLoad method on the main view controller are:
dbsingleton = [Database sharedInstance];
nmocontext = [dbsingleton managedObjectContext];
nmocontext is an ivar I use throughout the view controller. When the user wants to see the other view controller, with the table, I alloc-init it, then present it modally. (It has an NSFetchedResultsController that supplies the data from my store.) I've tried various strategies here:
I've made the NSFetchedResultsController a retained property set by the main view controller
I've made the NSManagedObjectContext a retained property set by the main view controller; and
I've used the singleton internally by repeating those two lines of code above at the beginning of the table view controller's viewDidLoad method.
Whichever I go with, the one problem I just can't solve is that after the user closes and deallocs the table view controller (and its NSFetchedResultsController), I start getting crashes in the main view controller when the store is accessed (like the "Illegal attempt" error mentioned above).
What are best practices for dealing with this common application pattern? I am still hoping to make this application iPhone SDK 3.x compatible, but I do seem to have fewer crashes when I'm using iOS 4 -- if there are underlying issues with 3.x that are causing me problems, please let me know and I may target iOS 4 only.
Thank you!
This is just a guess but I assume the following problem for your crashes after closing the tableview:
You declared a property
#property (retain, nonatomic) NSManagedObjectContext* nmocontext;
do you properly release the ivar nmocontext in dealloc?
If yes your problem is the assignment
nmocontext = [dbsingleton managedObjectContext];
This never retains nmocontext within your viewcontroller but you release it on dealloc.
Second:
"Illegal attempt to establish a relationship 'xyz' between objects in different contexts."
This is not a memory management issue, but you probably created another new context to add objects (like in the apple core data iphone samples) and tried to set a NSManagedObject as relationship from a different context.
It sounds like you don't have your singleton properly configured.
A singleton should override release to do nothing such that when it is sent a release message nothing happens. If you do not override release then any random piece of code anywhere in the app can kill the singleton and defeat the entire purpose of using a singleton. The next time you call singleton, you actually get another new object which in this case also returns a new managed object context.
See Cocoa Fundamentals Guide: Creating a Singleton Instance.
Singletons are powerful and flexible but very easy to do wrong. They are so easy to screw up that many experienced developers simply refuse to use them. If don't have experience with them, don't use them when you are just starting out.

How do I access the managedObjectContext from a controller deep in the UI?

I'm still a little fuzzy on understanding iPhone/Cocoa in general so this is probably a simple question.
I have a CoreData Window-Based App for the iPhone. The rootController is a UITabBarController. The first tab view has a UINavigationController attached to it with a table in it's main view.
When the App starts the objectContext is set up, which makes sense to have the App do that once. But now I have the managedObjectContext in the main Controller but I want to get that passed down to the Controller of the View inside the navcontroller, inside the first item in the TabBarController's tab list. How do I do this?
Would naming the one of the fields in the UI Inspector Tool allow me to do something like:
tabcontroller.navcontroller.manageObjectContext = self.managedObjectContext;
Would this only work if the controller was instantiated and 'live'. (Do the controllers not get instantiated until they are needed?) What if this was in a view that was for the most part hidden?
Anyway this is probably a simple thing I'm just not understanding things properly yet.
What is the general right way to share the manageObjectContext that is created and setup in the rootController to the many sub-controllers in the app?
I'm guessing this is the preferred method assuming the core-data initialization is done in the AppDelegate:
[[[UIApplication sharedApplication] delegate] managedObjectContext]
I usually give controllers a - (id)initWithManagedObjectContext:(NSManagedObjectContext *)context init method and a corresponding variable.
If a controller creates another controller in turn, it will, if needed, pass the NSManagedObjectContext to that controller in the same manner.
If you don't want to create an extra init method, just give the controllers a property for the NSManagedObjectContext and set that property directly after creating them.
I usually try to limit the number of controllers that directly deal with and "know about" Core Data though.
The answers to this question provide several means of accessing the Core Data stack deep within your application. As I indicate in one of the comments, I prefer to use a singleton DatabaseController that can be accessed from wherever, similar to how the NSUserDefaults' standardUserDefaults works.