Documentation Archive

Developer

Core Data Programming Guide

On This Page

Using Core Data with Cocoa Bindings

The Core Data framework focuses on the object model, while Cocoa bindings focus on the user interface. Cocoa bindings make it easy to keep the user interface properly synchronized and provide integration between the model, controller, and the user interface (the view in MVC). The Core Data framework is designed to interoperate seamlessly with and enhance the utility of Cocoa bindings. For more information, see Cocoa Bindings Reference.

In general, Cocoa bindings work in exactly the same way with Core Data managed objects as they do with other Cocoa model objects. Core Data uses the same predicate objects and sort descriptors that you would use against data that is not managed by Core Data—for example, to present data in a table view. These similarities between Core Data and Cocoa bindings give you a consistent API set to use throughout your application.

Core Data and Cocoa bindings have a few differences in configuration and operation that are discussed in this chapter. Also see Troubleshooting Core Data for details about these issues that may cause interaction problems between Cocoa bindings and Core Data:

With these exceptions, everything that is discussed and described in Cocoa Bindings Programming Topics applies equally to Core Data-based applications. Use the same techniques for configuring and debugging bindings, regardless of whether you are using Core Data.

Core Data Additions to Controllers

Core Data adds features to Cocoa bindings primarily in the configuration of controller objects such as NSObjectController and NSArrayController. Core Data adds the following features to those classes:

  • A reference to a managed object context that is used for all fetches, insertions, and deletions. If a controller's content is a managed object or collection of managed objects, you must either bind or set the managed object context for the controller.

  • An entity name that is used instead of the content object class to create new objects.

  • A reference to a fetch predicate that constrains what is fetched to set the content if the content is not set directly.

  • A content binding option (Deletes Objects On Remove) in Interface Builder that—if the content is bound to a relationship—specifies whether objects removed from the controller are deleted in addition to being removed from the relationship.

Automatically Prepares Content Flag

If the automaticallyPreparesContent flag (see, for example, setAutomaticallyPreparesContent:) is set (for example, in Interface Builder) for a controller, the controller's initial content is fetched from its managed object context using the controller's current fetch predicate. If the flag is not set, the data is not retrieved from the managed object context until prepareContent is called. The AutomaticallyPreparesContent flag is available on both the NSObjectController and the NSArrayController, regardless of whether Core Data is used.

It is important to note that the controller's fetch is executed as a delayed operation performed after the controller’s managed object context is set (by nib loading). Therefore, the fetch happens after awakeFromNib and windowControllerDidLoadNib: are called. The order of operations can create a problem if you want to perform an operation with the contents of an object controller in either of these methods, because the controller's content is nil. You can work around this by executing the fetch manually with fetchWithRequest:merge:error:. You pass nil as the fetch request argument to use the default request, as illustrated in the following code fragment:

Objective-C

  1. - (void)windowControllerDidLoadNib:(NSWindowController *)windowController
  2. {
  3. [super windowControllerDidLoadNib:windowController];
  4. NSArrayController *arrayController = [self arrayController];
  5. NSError *error = nil;
  6. BOOL ok = [arrayController fetchWithRequest:nil merge:NO error:&error];
  7. if (ok == NO) {
  8. NSLog(@"Failed to perform fetch: %@", error);
  9. abort();
  10. }
  11. }

Swift

  1. override func windowControllerDidLoadNib(windowController: NSWindowController) {
  2. super.windowControllerDidLoadNib(windowController)
  3. do {
  4. try arrayController.fetch(with: nil, merge: false)
  5. } catch {
  6. fatalError("Failed to perform fetch: \(error)")
  7. }
  8. }

Entity Inheritance

If you specify a parent entity in Core Data as the entity for a Core Data fetch request, the fetch returns matching instances of the entity and subentities (see Fetching Objects). As a corollary, if you specify a parent entity as the entity for a controller that is backed by Core Data, it fetches matching instances of the entity and any subentities. If you specify an abstract parent entity, the Core Data backed controller fetches matching instances of concrete subentities.

Filter Predicate for a to-Many Relationship

Sometimes you may want to set up a filter predicate for a search field that lets a user filter the contents of an array controller based on the destination of a to-many relationship. If you want to search a to-many relationship, you need to use an ANY or ALL operator in the predicate. For example, if you want to fetch departments in which at least one of the employees has the first name Matthew, you use an ANY operator as shown in the following example:

Objective-C

  1. NSPredicate *predicate = [NSPredicate predicateWithFormat:
  2. @"ANY employees.firstName like 'Matthew'"];

Swift

  1. let predicate = NSPredicate(format: "ANY employees.firstName like 'Matthew'", argumentArray: nil)

You use the same syntax in a search field's predicate binding:

  1. ANY employees.firstName like $value

Things are more complex, however, if you want to match a prefix or suffix or both—for example, if you want to look for departments in which at least one of the employees has the first name Matt, Matthew, Mattie, or any other name beginning with Matt. Fundamentally you simply need to add wildcard matching:

Objective-C

  1. NSPredicate *predicate = [NSPredicate predicateWithFormat:
  2. @"ANY employees.firstName like 'Matt*'"];

Swift

  1. let predicate = NSPredicate(format: "ANY employees.firstName like 'Matt*'", argumentArray: nil)

You cannot, though, use the same syntax within a search field's predicate binding:

  1. // does not work
  2. ANY employees.firstName like '$value*'

The reasons for this are described in Predicate Programming Guide—putting quotes in the predicate format prevents the variable substitution from happening. Instead, within a search field’s predicate binding, you must substitute any wildcards first, as illustrated in this example:

Objective-C

  1. NSString *value = @"Matt";
  2. NSString *wildcardedString = [NSString stringWithFormat:@"%@*", value];
  3. [[NSPredicate predicateWithFormat:@"ANY employees.firstName like %@", wildcardedString];

Swift

  1. let value = "Matt"
  2. let wildcardedString = "\(value)*"
  3. let predicate = NSPredicate(format: "ANY employees.firstName like %@", wildcardedString)

By implication, therefore, you must write some code to support the substitution of wildcards due to a limitation in the combination of Interface Builder and wildcard substitution variables in NSPredicate.