8
votes

I'm relative new with CoreData and I want to know if I'm doing the things right. First the documentation says:

"By convention, you get a context from a view controller. You must implement your application appropriately, though, to follow this pattern.

When you implement a view controller that integrates with Core Data, you can add an NSManagedObjectContext property.

When you create a view controller, you pass it the context it should use. You pass an existing context, or (in a situation where you want the new controller to manage a discrete set of edits) a new context that you create for it. It’s typically the responsibility of the application delegate to create a context to pass to the first view controller that’s displayed."
https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/CoreDataSnippets/Articles/stack.html

so what I do is create a property for my NSManagedObjectContext:

MyViewController.H
@interface MyViewController : ViewController
{
    NSManagedObjectContext *moc;
}

@property (nonatomic, retain) NSManagedObjectContext *moc;

@end

 MyViewController.m
 @implementation MyViewController
 @synthesize moc=moc;

1.-And any place I want to do some change to the database I do this.

MainNexarAppDelegate *appDelegate =
[[UIApplication sharedApplication] delegate];

self.moc = [[NSManagedObjectContext alloc] init];
self.moc.persistentStoreCoordinator = [appDelegate persistentStoreCoordinator];
/*code**/
[self.moc save:&error];

2-.And if I'm going to work in a different thread I have my custom method to create the NSManagedObjectContext with NSPrivateQueueConcurrencyType so it can be manage in a private queue:

   //Myclass NSObject<br>

 -(NSManagedObjectContext *)createManagedObjectContext{

    MainNexarAppDelegate *appDelegate =
    [[UIApplication sharedApplication] delegate];

    NSPersistentStoreCoordinator *coordinator = [appDelegate persistentStoreCoordinator];
    if (coordinator != nil) {
        __managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

        [__managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return __managedObjectContext;  
}
//__managedObjectContext is my property from the .h file 
//@property (readonly,strong,nonatomic)  NSManagedObjectContext* managedObjectContext;
  1. Is a good practice create a NSManagedObjectContext for each view controller where you will do some change to the database?
    1.1. It's a valid approach use [UIApplication sharedApplication] to get the persistent NSPersistentStoreCoordinator form the appdelegate?
  2. It's safe to share the persistent store coordinator between the main thread and any other thread?

Any help will be appreciated :).

5

5 Answers

3
votes

I am going to disagree with most of the answers here. It is NOT bad for #1. In fact, it is probably good practice in most cases to do so. Especially if you have different threads running stuff. It has greatly simplified my apps to create NSManagedObjectContexts whenever needed including per view controller. This is also recommended by the guys behind MagicalRecord (which is what I use to make use of Core Data in most cases). NSManagedObjectContext creation is not a high overhead call per the MR guys. I am not a CoreData expert by any stretch of the imagination, but I have had much better results in doing it this way, as recommended to me by the MagicalRecord guys.

1
votes

I can only provide help for issue #1. The following is an example of what the Apple docs mean when they say pass the context to your view controllers. In this case, the app delegate is passing the context to the root view controller after the app delegate creates the context.

// in AppDelegate.m (using storyboard)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.

    // let's assume that your MyViewController is the root view controller; grab a reference to the root view controller
    MyViewController *rootViewController = (MyViewController *)self.window.rootViewController;

    // initialize the Core Data stack...

    rootViewController.moc = ... // pass the context to your view controller

    return YES;
}
0
votes

It isn't necessary but it is a powerful technique to manage a discrete set of edits in a view controller that is going to be used to edit an item e.g. like in the snippet below from Apple's CoreDataBooks sample. The reason you would want to do this is you can use all of Core Data's features for updating your UI without having to undo everything on the main context if the user decides to cancel, the child context can simply be discarded. It is also possible for the parents changes to be pushed down to the children by means of NSManagedObjectContext's new automaticallyMergesChangesFromParent property.

RootViewController.m

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

    if ([[segue identifier] isEqualToString:@"AddBook"]) {

        /*
         The destination view controller for this segue is an AddViewController to manage addition of the book.
         This block creates a new managed object context as a child of the root view controller's context. It then creates a new book using the child context. This means that changes made to the book remain discrete from the application's managed object context until the book's context is saved.
          The root view controller sets itself as the delegate of the add controller so that it can be informed when the user has completed the add operation -- either saving or canceling (see addViewController:didFinishWithSave:).
         IMPORTANT: It's not necessary to use a second context for this. You could just use the existing context, which would simplify some of the code -- you wouldn't need to perform two saves, for example. This implementation, though, illustrates a pattern that may sometimes be useful (where you want to maintain a separate set of edits).
         */

        UINavigationController *navController = (UINavigationController *)[segue destinationViewController];
        AddViewController *addViewController = (AddViewController *)[navController topViewController];
        addViewController.delegate = self;

        // Create a new managed object context for the new book; set its parent to the fetched results controller's context.
        NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [addingContext setParentContext:[self.fetchedResultsController managedObjectContext]];

        Book *newBook = (Book *)[NSEntityDescription insertNewObjectForEntityForName:@"Book" inManagedObjectContext:addingContext];
        addViewController.book = newBook;
        addViewController.managedObjectContext = addingContext;
    }

    else if ([[segue identifier] isEqualToString:@"ShowSelectedBook"]) {

        NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
        Book *selectedBook = (Book *)[[self fetchedResultsController] objectAtIndexPath:indexPath];

        // Pass the selected book to the new view controller.
        DetailViewController *detailViewController = (DetailViewController *)[segue destinationViewController];
        detailViewController.book = selectedBook;
    }    
}

#pragma mark - Add controller delegate

/*
 Add controller's delegate method; informs the delegate that the add operation has completed, and indicates whether the user saved the new book.
 */
- (void)addViewController:(AddViewController *)controller didFinishWithSave:(BOOL)save {

    if (save) {
        /*
         The new book is associated with the add controller's managed object context.
         This means that any edits that are made don't affect the application's main managed object context -- it's a way of keeping disjoint edits in a separate scratchpad. Saving changes to that context, though, only push changes to the fetched results controller's context. To save the changes to the persistent store, you have to save the fetch results controller's context as well.
         */        
        NSError *error;
        NSManagedObjectContext *addingManagedObjectContext = [controller managedObjectContext];
        if (![addingManagedObjectContext save:&error]) {
            /*
             Replace this implementation with code to handle the error appropriately.

             abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
             */
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }

        if (![[self.fetchedResultsController managedObjectContext] save:&error]) {
            /*
             Replace this implementation with code to handle the error appropriately.

             abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
             */
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }

    // Dismiss the modal view to return to the main list
    [self dismissViewControllerAnimated:YES completion:nil];
}
-1
votes
  1. No, it's not good to create NSManagedObjectContext for each controller. All you need is to have own context for each thread. So it depends on your logic. 1.1. Yes, it's not bad.
  2. Yes, it is safe.

In my apps I use a singleton class with shared NSPersistentStoreCoordinator. And if I need to create new context, I use

self.context = [NSManagedObjectContext new];
self.context.persistentStoreCoordinator = [[SharedStorage sharedStorage] storeCoordinator];

A bit detailed code snippet here. Usually my view controllers which use NSManagedObjectContext have table views, so I use NSFetchedResultsController. And I use only one shared context for all this controllers.

Note 1: Some say it's a bad idea to have singletons.

Note 2: Don't forget that you need to synchronize all your contexts via save and merge methods.

-1
votes

Bad: To create NSManagedObjectContexts for each viewController which is going to make some changes, in the database Good: Create a single NSManagedObjectContext aka singleton, which will be passed in to those view controllers which want to make some changes int the database, The implication of this is that, since the app basically has single database although you can have multiple databases in one app. Example1: Suppose you created a Tab based app then rootViewController of the window will be the UITabBarController, from the root you can get all other controllers! and here you can pass them the context

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    /* 
        MUNSharedDatabaseController is the singleton
     */
    UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
    UIViewController *firstViewController = tabBarController.viewControllers[0];
    firstViewController.managedObjectContext = [[MUNSharedDatabaseController sharedDatabaseController] managedObjectContext];
    UIViewController *secondViewController = tabBarController.viewControllers[1];
    secondVC.managedObjectContext = [[MUNSharedDatabaseController sharedDatabaseController] managedObjectContext];
    // otherStuff, or etc.
}

Also there is great Core Data library, aka MagicalRecord You can check it here: https://github.com/magicalpanda/MagicalRecord if you want to save time, it real great, but its not a replacement of Core Data . Also there is an example how to create a core data singleton here: http://steamheadstudio.pl/wordpress/core-data-singleton-with-sample-code/