Skip to content

Fetched properties #15

Description

@ashalkhakov

A fetched property is essentially a stored NSFetchRequest attached to a specific entity. Instead of being backed by a physical foreign key in the database like a standard relationship, it is evaluated dynamically at runtime whenever it is accessed.

Conceptually, you can think of it as a "soft link" or a lazy, read-only relationship. It is always represented as an NSArray (unlike standard To-Many relationships, which are NSSets).

How they differ from standard relationships

  • No Schema Impact: They require no foreign keys, no join tables, and no SQLite triggers. They exist entirely in the model graph and at runtime.
  • No Inverse: Because they are just dynamic queries, they cannot have an inverse relationship.
  • Read-Only: You cannot add or remove objects directly from a fetched property array. To change the results, you must modify the underlying objects so they either match or fail the predicate, and then refresh the property.
  • Cross-Store Linking (Legacy): Historically, CoreData allowed fetched properties to query across different persistent stores (e.g., an XML store to a SQLite store), which is impossible with standard relationships.
  • Because fetched properties execute a discrete NSFetchRequest against the store, they cannot be pre-fetched using relationshipKeyPathsForPrefetching.
  • Fetched properties are completely ignored during -[NSManagedObject validateForUpdate:] or -[NSManagedObject validateForInsert:]. Because they are read-only and don't persist foreign keys, the context doesn't care about their state when saving.

How it works at runtime

When a user accesses a fetched property (e.g., NSArray *results = myObject.recentInvoices;), the NSManagedObject intercepts the call via Key-Value Coding or dynamic method resolution.

The framework then performs the following steps:

  1. Retrieves the Request: It looks up the NSFetchedPropertyDescription in the model to get the underlying NSFetchRequest.
  2. Variable Substitution: Fetched properties use a special variable substitution in their predicates. The framework injects the current object into the predicate using the $FETCH_SOURCE variable. (e.g., A predicate of customerId == $FETCH_SOURCE.id becomes customerId == 123).
  3. Execution: It executes the fetch request against the object's NSManagedObjectContext.
  4. Caching: CoreData caches the resulting NSArray. Subsequent accesses will return the cached array without hitting the database again, unless the object is turned back into a fault or the context is explicitly refreshed.

Implementation Strategy

To implement this without touching the SQLite store, need to handle the lazy-loading at the NSManagedObject level.

  • NSFetchedPropertyDescription: new subclass of NSPropertyDescription. It needs an NSFetchRequest property.
  • Dynamic Access: In the NSManagedObject implementation of valueForKey:
    • Check if the requested key corresponds to an NSFetchedPropertyDescription.
    • If it does, check the internal value of the property to see if it has already been evaluated.
    • If cached, return it.
  • Evaluation: If not cached:
    • Copy the NSFetchRequest from the description.
    • Use [NSPredicate predicateWithSubstitutionVariables:@{@"FETCH_SOURCE": self}] to bind the predicate.
    • Call [self.managedObjectContext executeFetchRequest:error:].
    • Store the resulting array as the internal property value and return it.
  • Cache Invalidation: When [self refreshObject:mergeChanges:] is called, clear the property value, so it re-evaluates on the next access.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions