How to pass reference to NSPersistentContainer to storyboard-created table view controller in objective c

I'm trying to follow the steps here https://developer.apple.com/documentation/coredata/setting_up_a_core_data_stack?language=objc for setting up core data using objective c, using the boilerplate given by xcode when opting into core data, but it appears to only show swift. The main view controller I have is of class TodoListTableViewController, which is storyboard-generated, which is embedded in a navigation controller like so:

entrypoint->navigation controller->TodoListTableViewController

I tried defining my AppDelegate::applicationDidFinishLaunchingWithOptions like so:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        if (self.persistentContainer == nil) {
        [NSException raise:@"did not initialize persistent container in app delegate" format:@"no init"];
    }

    UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    TodoListTableViewController *todoListVC = [mainStoryboard instantiateViewControllerWithIdentifier:@"TodoListTableViewController"];
    todoListVC.persistentContainer = self.persistentContainer;
    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:todoListVC];
    self.window.rootViewController = navController;
    
    return YES;
}

and using the boilerplate in AppDelegate

@synthesize persistentContainer = _persistentContainer;

- (NSPersistentContainer *)persistentContainer {

    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.

    @synchronized (self) {

        if (_persistentContainer == nil) {

            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"ToDoList"];

            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {

                if (error != nil) {

                    // 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.

                    

                    /*

                     Typical reasons for an error here include:

                     * The parent directory does not exist, cannot be created, or disallows writing.

                     * The persistent store is not accessible, due to permissions or data protection when the device is locked.

                     * The device is out of space.

                     * The store could not be migrated to the current model version.

                     Check the error message to determine what the actual problem was.

                    */

                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);

                    abort();

                }

            }];

        }

    }

    

    return _persistentContainer;

}



#pragma mark - Core Data Saving support



- (void)saveContext {

    NSManagedObjectContext *context = self.persistentContainer.viewContext;

    NSError *error = nil;

    if ([context hasChanges] && ![context 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();

    }

}

but I'm getting a missing container exception since I added a check for the existence of persistentContainer in TodoListTableViewController like so:

- (void)viewDidLoad {
   [super viewDidLoad];
   if (self.persistentContainer == nil) {
     [NSException raise:@"missing container" format:@"missing container"];
   }
}

thank you.

Replies

Storyboards and dependency injection were traditionally not the best friends. We fixed that in iOS 13 with the addition of -instantiateInitialViewControllerWithCreator: and -instantiateViewControllerWithIdentifier:creator:. These let you pass additional state, like a managed object context, to your view controller in a controlled manner.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

@eskimo thanks. when trying to use the creator method, it looks like I'm only allowed to pass an NSCoder in the block. Can you provide an example of how I might fix this to include persistentContainer assignment?

    ToDoListTableViewController *todoListVC = [mainSB instantiateViewControllerWithIdentifier:@"ToDoListTableViewController"
                                                                                    creator:^UIViewController * _Nullable(NSCoder * _Nonnull coder) {
                                                                                        return [[ToDoListTableViewController alloc] initWithCoder:coder];
                                                                                    }];
     todoListVC.persistentContainer = self.persistentContainer;

Blocks can capture context from outside. Here’s an example from a project I have lying around (sorry it’s in Swift):

class MyClass … {

    let log = Logger(subsystem: "com.example.apple-samplecode.Test727949", category: "app")

    func myMethod() {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        self.main = storyboard.instantiateViewController(identifier: "main") { coder in
            MainViewController(coder: coder, log: self.log)
        }
    }
}

Note how the block captures self so that it can pass self.log to the view controller’s initialiser.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

after having added breakpoints, I'm seeing that the initWithCoder that I overrode is being called twice by two different instances of the TodoListTableViewController, which makes me think only one of these is a reference to the one I added using storyboard. I'm not sure if this helps with identifying the issue. Thanks

Declare a initializer like this in your ToDoListTableViewController header file:

-(instancetype)initWithCoder:(NSCoder*)coder persistentContainer:(NSPersistentContainer*)persistentContainer;

Then in your implementation:

-(instancetype)initWithCoder:(NSCoder*)coder persistentContainer:(NSPersistentContainer*)persistentContainer
{
   self = [super initWithCoder:coder];
   if (self)
   {
        _persistentContainer = persistentContainer;
   }
   return self;
}

Then you can do this:


UIStoryboard *mainSB = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
 ToDoListTableViewController *toDOVC = [mainSB instantiateViewControllerWithIdentifier:@"ToDoListTableViewController"
                                                                                    creator:^UIViewController * _Nullable(NSCoder * _Nonnull coder) 
     {
          return [[ToDoListTableViewController alloc]initWithCoder:coder persistentContainer:persistentContainer];
      }];

But I wouldn't stop there. You could make the code cleaner by also declaring a factory method in ToDoListTableViewController so you can write the UIStoryboard code from many different places:

//In ToDoListTableViewController's header file:

+(ToDoListTableViewController*)makeWithPersistentContainer:(NSPersistentContainer*)persistentContainer;

//Implementation:
+(ToDoListTableViewController*)makeWithPersistentContainer:(NSPersistentContainer*)persistentContainer;
{
     UIStoryboard *mainSB = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
     return [mainSB instantiateViewControllerWithIdentifier:@"ToDoListTableViewController"
                                                                                    creator:^UIViewController * _Nullable(NSCoder * _Nonnull coder) 
     {
          return [[ToDoListTableViewController alloc]initWithCoder:coder persistentContainer:persistentContainer];
      }];
}

Then you can just use the +makeWithPersistentContainer: to make the ToDoListTableViewController. Alternatively you could look for documentation on the hidden IBSegueAction feature but it's probably not necessary.