Inherits from RKObjectRequestOperation : NSOperation
Declared in RKManagedObjectRequestOperation.h

Overview

RKManagedObjectRequestOperation is a subclass of RKObjectRequestOperation that implements object mapping on the response body of an NSHTTPResponse loaded via an RKHTTPRequestOperation in which the mapping targets NSManagedObject objects managed by Core Data.

The RKManagedObjectRequestOperation class extends the basic behavior of an RKObjectRequestOperation to meet the constraints imposed by Core Data. In particular, managed object request operations observe the threading requirements by making use of NSManagedObjectContext concurrency types, leverage the support for parent/child contexts, and handle obtaining a permanent NSManagedObjectID for objects being mapped so that they are addressable across contexts. Object mapping is internally performed within a block provided to the target context via performBlockAndWait:, ensuring execution on the appropriate queue.

Aside from providing the basic infrastructure for successful object mapping into Core Data, a number of additional Core Data specific features are provided by the RKManagedObjectRequestOperation class that warrant discussion in detail.

Parent Context

Every RKManagedObjectRequestOperation object must be assigned an NSManagedObjectContext in which to persist the results of the underlying object mapping operation. This context is used as the parent context for a new, private NSManagedObjectContext with a concurrency type of NSPrivateQueueConcurrencyType in which the object mapping is actually performed. The use of this parent context has a number of benefits:

  1. If the context that was assigned to the managed object request operation has a concurrency type of NSMainQueueConcurrencyType, then directly mapping into the context would block the execution of the main thread for the duration of the mapping process. The use of the private child context isolates the mapping process from the main thread entirely.
  2. In the event of an error, the private context can be discarded, leaving the state of the parent context unchanged. On successful completion, the private context is saved and ‘pushes’ its changes up one level into the parent context.

Permanent Managed Object IDs

One of the confounding factors when working with asycnhronous processes interacting with Core Data is the addressability of managed objects that have not been saved to the persistent store across contexts. Unpersisted NSManagedObject instances have an objectID that is temporary and unsuitable for use in uniquely addressing a given object across two managed object contexts, even if they have common ancestry and share a persistent store coordinator. To mitigate this addressability issue without requiring objects to be saved to the persistent store, managed object request operations invoke obtainPermanentIDsForObjects: on the operation’s target object (if any) and all managed objects that were inserted into the context during the mapping process. By the time the operation finishes, all managed objects in the mapping result can be referenced by objectID across contexts with no further action.

Identification Attributes & Managed Object Caching

When object mapping managed objects it is necessary to differentiate between objects that already exist in the local store and those that are being created as part of the mapping process. This ensures that the local store does not become populated with duplicate records. To make this differentiation, RestKit requires that each RKEntityMapping be configured with one or more identification attributes. Each identification attribute must correspond to a static attribute assigned by the remote backend system. During mapping, these attributes are used to search the managed object context for an existing managed object. If one is found, the object is updated else a new object is created. Identification attributes are configured on the [RKEntityMapping identificationAttributes] property.

Identification attributes are used in conjunction with the RKManagedObjectCaching protocol. Each managed object request operation is associated with an object conforming to the RKManagedObjectCaching protocol via the managedObjectCache proeprty. This cache is consulted during mapping to find existing objects and when establishing relationships between entities by one or more attributes. Please see the documentation accompanying RKManagedObjectCaching and RKConnectionDescription classes for more information.

Deleting Managed Objects for DELETE requests

RKManagedObjectRequestOperation adds special behavior to DELETE requests. Upon retrieving a successful (2xx status code) response for a DELETE, the operation will invoke deleteObject: with the operations targetObject on the managed object context. This will delete the target object from the local store in conjunction the successfully deleted remote representation.

Fetch Request Blocks and Deleting Orphaned Objects

A common problem when accessing remote resources representing collections of objects is the problem of deletion of remote objects. The RKManagedObjectRequestOperation class provides support for the cleanup of such orphaned objects. In order to utilize the functionality, the operation must be able to compare a set of reference objects to the retrieved payload in order to determine which objects exist in the local store, but are no longer being returned by the server. This reference set of objects is built by executing an NSFetchRequest that corresponds to the URL being loaded. Configuration of this fetch request is done via an RKFetchRequestBlock block object. This block takes a single NSURL argument and returns an NSFetchRequest objects. An array of these blocks can be provided to the managed object request operation and the array will be searched in reverse order until a non-nil value is returned by a block. Any block that cannot build a fetch request for the given URL is expected to return nil to indicate that it does not match the given URL. Processing of the URL is typically performed using an RKPathMatcher object against the value returned from the relativePath or relativeString methods of NSURL.

To illustrate this concept, please consider the following real-world example which builds a fetch request for retrieving the Terminals that exist in an Airport:

RKObjectManager *manager = [RKObjectManager managerWithBaseURL:@"http://restkit.org"]http://restkit.org"];
[manager addFetchRequestBlock:^NSFetchRequest *(NSURL *URL) {
    RKPathMatcher *pathMatcher = [RKPathMatcher pathMatcherWithPattern:@"/airports/:airport_id/terminals.json"];
    NSDictionary *argsDict = nil;
    BOOL match = [pathMatcher matchesPath:[URL relativePath] tokenizeQueryStrings:NO parsedArguments:&argsDict];
    NSString *airportID;
    if (match) {
        airportID = [argsDict objectForKey:@"airport_id"];
        NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Terminal"];
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Airport" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
        fetchRequest.predicate = [NSPredication predicateWithFormat:@"airportID = %@", @([airportID integerValue])]; // NOTE: Coerced from string to number
        fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES] ];
        return fetchRequest;
    }
    return nil;
}];

The above example code defines an RKFetchRequestBlock block object that will match an NSURL with a relative path matching the pattern @"/airports/:airport_id/terminals.json". If a match is found, the block extracts the airport_id key from the matched arguments, coerces its value into a number, and uses it to construct an NSPredicate for the primary key attribute of GGAirport entity. Take note that the value of the ‘airport_id’ was coerced from an NSString to an NSNumber — failure to so would result in a predicate whose value is equal to airportID == '1234' vs. airportID == 1234, which will prevent fetch requests from evaluating correctly. Once coerced, the value is used to construct a NSFetchRequest object for the GGTerminal entity that will retrieve all the managed objects with an airport ID attribute whose value is equal to airport_id encoded within the URL’s path.

In more concrete terms, given the URL http://restkit.org/airports/1234/terminals.json the block would return an NSFetchRequest equal to:

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Terminal"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"airportID = 1234"];
fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]];

Once configured and registered with the object manager, any RKManagedObjectRequestOperation created through the manager will automatically consult the fetch request blocks and perform orphaned object cleanup. No cleanup is performed if no block in the fetchRequestBlocks property is found to match the URL of the request.

Managed Object Context Save Behaviors

The results of the operation can either be ‘pushed’ to the parent context or saved to the persistent store. Configuration is available via the savesToPersistentStore property. If an error is encountered while saving the managed object context, then the operation is considered to have failed and the error property will be set to the NSError object returned by the failed save.

304 ‘Not Modified’ Responses

In the event that a managed object request operation loads a 304 ‘Not Modified’ response for an HTTP request no object mapping is performed as Core Data is assumed to contain a managed object representation of the resource requested. No object mapping is performed on the cached response body, making a cache hit for a managed object request operation a very lightweight operation. To build the mapping result returned to the caller, all of the fetch request blocks matching the request URL will be invoked and each fetch request returned is executed against the managed object context and the objects returned are added to the mapping result. Please note that all managed objects returned in the mapping result for a ‘Not Modified’ response will be returned under the [NSNull null] key path.

Note that NSURLConnection supports conditional GET transparently when the cache policy is set to NSURLRequestUseProtocolCachePolicy. Because of this the NSHTTPURLResponse loaded does not have the 304 (Not Modified) status code. In order to determine if a 304 response has resulted in the loading of an existing response from NSURLCache, the managed object request operation evaluates the following heuristic on the response:

  1. Before the HTTP request is loaded, a reference to any existing NSCachedURLResponse is obtained.
  2. When the response is loaded, the request is evaluated for cacheability. A request is considered cacheable if and only if its HTTP method is either “GET” or “HEAD” and its status code is 200, 304, 203, 300, 301, 302, 307, or 410.
  3. If the request is found to be cacheable, the Etag of the current response is matched against the reference to the existing cache entry obtained before the request was loaded.
  4. If the Etags match, the response data of the loaded response is matched against the cache entry reference.
  5. If the data is found to match, then the userInfo dictionary of the cache entry for the current request is checked for the existence of Boolean value under the RKResponseHasBeenMappedCacheUserInfoKey key. If the value of this key is YES, it indicates that the response was previously mapped to completion by an object request operation.

If this heuristic evaluates positively, then the response is determined to have been loaded from the cache and no mapping or managed object deletion cleanup is performed. This optimization greatly improves performance in applications where HTTP caching is leveraged.

Subclassing Notes

This class relies on the following RKMapperOperationDelegate method methods to do its work:

  1. mapperDidFinishMapping:

If you subclass RKManagedObjectRequestOperation and implement any of the above methods then you must call the superclass implementation.

Limitations and Caveats

  1. RKManagedObjectRequestOperation does NOT support object mapping that targets an NSManagedObjectContext with a concurrencyType of NSConfinementConcurrencyType.
  2. RKManagedObjectRequestOperation can become deadlocked if configured to perform mapping onto an NSManagedObjectContext with the NSMainQueueConcurrencyType and is invoked synchronously from the main thread via start or an attempt is made to await completion of the operation via waitUntilFinished. This occurs because managed object contexts with the NSMainQueueConcurrencyType are dependent upon the execution of the main thread’s run loop to perform their work and waitUntilFinished blocks the calling thread, leading to a deadlock when called from the main thread. Rather than awaiting completion of the operation via waitUntilFinishes, consider using a completion block, key-value observation, or spinning the run-loop via [[NSRunLoop currentRunLoop] runUntilDate:].

Tasks

Configuring Core Data Integration

  •   managedObjectContext

    The managed object context associated with the managed object request operation.

    property
  •   managedObjectCache

    The managed object cache associated with the managed object request operation.

    property
  •   fetchRequestBlocks

    An array of RKFetchRequestBlock block objects used to map NSURL objects into corresponding NSFetchRequest objects.

    property

Managing Completion Behaviors

  •   deletesOrphanedObjects

    A Boolean value that determines if the receiver will delete orphaned objects upon completion of the operation.

    property
  •   savesToPersistentStore

    A Boolean value that determines if the operation saves the mapping results to the persistent store upon successful completion. If the network transport or mapping portions of the operation fail the operation then this option has no effect.

    property

Properties

deletesOrphanedObjects

A Boolean value that determines if the receiver will delete orphaned objects upon completion of the operation.

@property (nonatomic, assign) BOOL deletesOrphanedObjects

Discussion

Please see the above discussion of ‘Deleting Managed Objects for DELETE requests’ for more details.

Default: YES

Declared In

RKManagedObjectRequestOperation.h

fetchRequestBlocks

An array of RKFetchRequestBlock block objects used to map NSURL objects into corresponding NSFetchRequest objects.

@property (nonatomic, copy) NSArray *fetchRequestBlocks

Discussion

Fetch requests corresponding to URL’s are used when deleting orphaned objects and completing object request operations in which avoidsNetworkAccess has been set to YES. Please see the above discussion of ‘Fetch Request Blocks’ for more details.

Declared In

RKManagedObjectRequestOperation.h

managedObjectCache

The managed object cache associated with the managed object request operation.

@property (nonatomic, weak) id<RKManagedObjectCaching> managedObjectCache

Discussion

The cache is used to look up existing objects by primary key to prevent the creation of duplicate objects during mapping. Please see the above discussion of ‘Managed Object Caching’ for more details.

Warning: A nil value for the managedObjectCache property is valid, but may result in the creation of duplicate objects.

Declared In

RKManagedObjectRequestOperation.h

managedObjectContext

The managed object context associated with the managed object request operation.

@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext

Discussion

This context acts as the parent context for a private managed object context in which the object mapping is performed and changes will be saved to this context upon successful completion of the operation.

Please see the above discussion about ‘Parent Context’ for details.

Declared In

RKManagedObjectRequestOperation.h

savesToPersistentStore

A Boolean value that determines if the operation saves the mapping results to the persistent store upon successful completion. If the network transport or mapping portions of the operation fail the operation then this option has no effect.

@property (nonatomic, assign) BOOL savesToPersistentStore

Discussion

When YES, the receiver will invoke saveToPersistentStore: on its private managed object context to persist the mapping results all the way back to the persistent store coordinator. If NO, the private mapping context will be saved causing the mapped objects to be ‘pushed’ to the parent context as represented by the managedObjectContext property.

Default: YES

Declared In

RKManagedObjectRequestOperation.h