RKManagedObjectRequestOperation Class Reference
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:
- 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. - 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:
- Before the HTTP request is loaded, a reference to any existing
NSCachedURLResponse
is obtained. - 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.
- 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.
- If the Etags match, the response data of the loaded response is matched against the cache entry reference.
- 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 theRKResponseHasBeenMappedCacheUserInfoKey
key. If the value of this key isYES
, 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:
mapperDidFinishMapping:
If you subclass RKManagedObjectRequestOperation
and implement any of the above methods then you must call the superclass implementation.
Limitations and Caveats
RKManagedObjectRequestOperation
does NOT support object mapping that targets anNSManagedObjectContext
with aconcurrencyType
ofNSConfinementConcurrencyType
.RKManagedObjectRequestOperation
can become deadlocked if configured to perform mapping onto anNSManagedObjectContext
with theNSMainQueueConcurrencyType
and is invoked synchronously from the main thread viastart
or an attempt is made to await completion of the operation viawaitUntilFinished
. This occurs because managed object contexts with theNSMainQueueConcurrencyType
are dependent upon the execution of the main thread’s run loop to perform their work andwaitUntilFinished
blocks the calling thread, leading to a deadlock when called from the main thread. Rather than awaiting completion of the operation viawaitUntilFinishes
, 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
propertyRKFetchRequestBlock
block objects used to mapNSURL
objects into correspondingNSFetchRequest
objects.
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