Skip to content

Commit 862e84a

Browse files
committed
Hook the RKManagedObjectMappingOperationDataSource into the Managed Object Context save lifecycle to avoid the creation of duplicate objects during sequential mapping operations
1 parent 97b60e6 commit 862e84a

12 files changed

+226
-108
lines changed

Code/CoreData/RKManagedObjectImporter.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ - (NSUInteger)importObjectsFromFileAtPath:(NSString *)path withMapping:(RKMappin
213213
}
214214

215215
NSDictionary *mappingDictionary = @{ (keyPath ?: [NSNull null]) : mapping };
216-
RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:parsedData mappingsDictionary:mappingDictionary];
216+
RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData mappingsDictionary:mappingDictionary];
217217
mapper.mappingOperationDataSource = self.mappingOperationDataSource;
218218
__block RKMappingResult *mappingResult;
219219
[self.managedObjectContext performBlockAndWait:^{

Code/CoreData/RKManagedObjectMappingOperationDataSource.m

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,21 @@ - (id)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContex
136136
if (self) {
137137
self.managedObjectContext = managedObjectContext;
138138
self.managedObjectCache = managedObjectCache;
139+
140+
[[NSNotificationCenter defaultCenter] addObserver:self
141+
selector:@selector(updateCacheWithChangesFromContextWillSaveNotification:)
142+
name:NSManagedObjectContextWillSaveNotification
143+
object:managedObjectContext];
139144
}
140145

141146
return self;
142147
}
143148

149+
- (void)dealloc
150+
{
151+
[[NSNotificationCenter defaultCenter] removeObserver:self];
152+
}
153+
144154
- (id)mappingOperation:(RKMappingOperation *)mappingOperation targetObjectForRepresentation:(NSDictionary *)representation withMapping:(RKObjectMapping *)mapping
145155
{
146156
NSAssert(representation, @"Mappable data cannot be nil");
@@ -239,4 +249,35 @@ - (BOOL)commitChangesForMappingOperation:(RKMappingOperation *)mappingOperation
239249
return YES;
240250
}
241251

252+
// NOTE: In theory we should be able to use the userInfo dictionary, but the dictionary was coming in empty (12/18/2012)
253+
- (void)updateCacheWithChangesFromContextWillSaveNotification:(NSNotification *)notification
254+
{
255+
NSSet *objectsToAdd = [[self.managedObjectContext insertedObjects] setByAddingObjectsFromSet:[self.managedObjectContext updatedObjects]];
256+
257+
__block BOOL success;
258+
__block NSError *error = nil;
259+
[self.managedObjectContext performBlockAndWait:^{
260+
success = [self.managedObjectContext obtainPermanentIDsForObjects:[objectsToAdd allObjects] error:&error];
261+
}];
262+
263+
if (! success) {
264+
RKLogWarning(@"Failed obtaining permanent managed object ID's for %ld objects: the managed object cache was not updated and duplicate objects may be created.", (long) [objectsToAdd count]);
265+
RKLogError(@"Obtaining permanent managed object IDs failed with error: %@", error);
266+
return;
267+
}
268+
269+
// Update the cache
270+
if ([self.managedObjectCache respondsToSelector:@selector(didFetchObject:)]) {
271+
for (NSManagedObject *managedObject in objectsToAdd) {
272+
[self.managedObjectCache didFetchObject:managedObject];
273+
}
274+
}
275+
276+
if ([self.managedObjectCache respondsToSelector:@selector(didDeleteObject::)]) {
277+
for (NSManagedObject *managedObject in [self.managedObjectContext deletedObjects]) {
278+
[self.managedObjectCache didDeleteObject:managedObject];
279+
}
280+
}
281+
}
282+
242283
@end

Code/Network/RKResponseMapperOperation.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ @implementation RKObjectResponseMapperOperation
287287
- (RKMappingResult *)performMappingWithObject:(id)sourceObject error:(NSError **)error
288288
{
289289
RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
290-
self.mapperOperation = [[RKMapperOperation alloc] initWithObject:sourceObject mappingsDictionary:self.responseMappingsDictionary];
290+
self.mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:sourceObject mappingsDictionary:self.responseMappingsDictionary];
291291
self.mapperOperation.mappingOperationDataSource = dataSource;
292292
if (NSLocationInRange(self.response.statusCode, RKStatusCodeRangeForClass(RKStatusCodeClassSuccessful))) {
293293
self.mapperOperation.targetObject = self.targetObject;
@@ -327,7 +327,7 @@ - (RKMappingResult *)performMappingWithObject:(id)sourceObject error:(NSError **
327327
self.operationQueue = [NSOperationQueue new];
328328
[self.managedObjectContext performBlockAndWait:^{
329329
// Configure the mapper
330-
self.mapperOperation = [[RKMapperOperation alloc] initWithObject:sourceObject mappingsDictionary:self.responseMappingsDictionary];
330+
self.mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:sourceObject mappingsDictionary:self.responseMappingsDictionary];
331331
self.mapperOperation.delegate = self.mapperDelegate;
332332

333333
// Configure a data source to defer execution of connection operations until mapping is complete

Code/ObjectMapping/RKMapperOperation.h

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
3838
## Mappings Dictionary
3939
40-
The mappings dictionary describes how to object map the source object. The keys of the dictionary are key paths into the `sourceObject` and the values are `RKMapping` objects describing how to map the representations at the corresponding key path. This dictionary based approach enables a single document to contain an arbitrary number of object representations that can be mapped independently. Consider the following example JSON structure:
40+
The mappings dictionary describes how to object map the source object. The keys of the dictionary are key paths into the `representation` and the values are `RKMapping` objects describing how to map the representations at the corresponding key path. This dictionary based approach enables a single document to contain an arbitrary number of object representations that can be mapped independently. Consider the following example JSON structure:
4141
4242
{ "tags": [ "hacking", "phreaking" ], "authors": [ "Captain Crunch", "Emmanuel Goldstein" ], "magazine": { "title": "2600 The Hacker Quarterly" } }
4343
@@ -52,15 +52,15 @@
5252
5353
### The NSNull Key
5454
55-
A mapping set for the key `[NSNull null]` value has special significance to the mapper operation. When a mapping is encountered with the a null key, the entire `sourceObject` is processed using the given mapping. This provides support for mapping content that does not have an outer nesting attribute.
55+
A mapping set for the key `[NSNull null]` value has special significance to the mapper operation. When a mapping is encountered with the a null key, the entire `representation` is processed using the given mapping. This provides support for mapping content that does not have an outer nesting attribute.
5656
5757
## Data Source
5858
5959
The data source is used to instantiate new objects or find existing objects to be updated during the mapping process. The object set as the `mappingOperationDataSource` will be set as the `dataSource` for the `RKMappingOperation` objects created by the mapper.
6060
6161
## Target Object
6262
63-
If a `targetObject` is configured on the mapper operation, all mapping work on the `sourceObject` will target the specified object. For transient `NSObject` mappings, this ensures that the properties of an existing object are updated rather than an new object being created for the mapped representation. If an array of representations is being processed and a `targetObject` is provided, it must be a mutable collection object else an exception will be raised.
63+
If a `targetObject` is configured on the mapper operation, all mapping work on the `representation` will target the specified object. For transient `NSObject` mappings, this ensures that the properties of an existing object are updated rather than an new object being created for the mapped representation. If an array of representations is being processed and a `targetObject` is provided, it must be a mutable collection object else an exception will be raised.
6464
6565
## Core Data
6666
@@ -75,11 +75,11 @@
7575
/**
7676
Initializes the operation with a source object and a mappings dictionary.
7777
78-
@param object An `NSDictionary` or `NSArray` of `NSDictionary` object representations to be mapped into local domain objects.
78+
@param representation An `NSDictionary` or `NSArray` of `NSDictionary` object representations to be mapped into local domain objects.
7979
@param mappingsDictionary An `NSDictionary` wherein the keys are mappable key paths in `object` and the values are `RKMapping` objects specifying how the representations at its key path are to be mapped.
8080
@return The receiver, initialized with the given object and and dictionary of key paths to mappings.
8181
*/
82-
- (id)initWithObject:(id)object mappingsDictionary:(NSDictionary *)mappingsDictionary;
82+
- (id)initWithRepresentation:(id)representation mappingsDictionary:(NSDictionary *)mappingsDictionary;
8383

8484
///------------------------------------------
8585
/// @name Accessing Mapping Result and Errors
@@ -100,14 +100,14 @@
100100
///-------------------------------------
101101

102102
/**
103-
The source object representation against which the mapping is performed.
103+
The representation of one or more objects against which the mapping is performed.
104104
105105
Either an `NSDictionary` or an `NSArray` of `NSDictionary` objects.
106106
*/
107-
@property (nonatomic, strong, readonly) id sourceObject;
107+
@property (nonatomic, strong, readonly) id representation;
108108

109109
/**
110-
A dictionary of key paths to `RKMapping` objects specifying how object representations in the `sourceObject` are to be mapped.
110+
A dictionary of key paths to `RKMapping` objects specifying how object representations in the `representation` are to be mapped.
111111
112112
Please see the above discussion for in-depth details about the mappings dictionary.
113113
*/
@@ -130,6 +130,8 @@
130130
*/
131131
@property (nonatomic, weak) id<RKMapperOperationDelegate> delegate;
132132

133+
- (BOOL)execute:(NSError **)error;
134+
133135
@end
134136

135137
///--------------------------------------
@@ -177,7 +179,7 @@
177179
178180
@param mapper The mapper operation performing the mapping.
179181
@param dictionaryOrArrayOfDictionaries The `NSDictictionary` or `NSArray` of `NSDictionary` object representations that was found at the `keyPath`.
180-
@param keyPath The key path that the representation was read from in the `sourceObject`. If the `keyPath` was `[NSNull null]` in the `mappingsDictionary`, it will be given as `nil` to the delegate.
182+
@param keyPath The key path that the representation was read from in the `representation`. If the `keyPath` was `[NSNull null]` in the `mappingsDictionary`, it will be given as `nil` to the delegate.
181183
*/
182184
- (void)mapper:(RKMapperOperation *)mapper didFindRepresentationOrArrayOfRepresentations:(id)dictionaryOrArrayOfDictionaries atKeyPath:(NSString *)keyPath;
183185

@@ -194,11 +196,11 @@
194196
///----------------------------------------------
195197

196198
/**
197-
Tells the delegate that the mapper is about to start a mapping operation to map a representation found in the `sourceObject`.
199+
Tells the delegate that the mapper is about to start a mapping operation to map a representation found in the `representation`.
198200
199201
@param mapper The mapper operation performing the mapping.
200202
@param mappingOperation The mapping operation that is about to be started.
201-
@param keyPath The key path that was mapped. A `nil` key path indicates that the mapping matched the entire `sourceObject`.
203+
@param keyPath The key path that was mapped. A `nil` key path indicates that the mapping matched the entire `representation`.
202204
*/
203205
- (void)mapper:(RKMapperOperation *)mapper willStartMappingOperation:(RKMappingOperation *)mappingOperation forKeyPath:(NSString *)keyPath;
204206

@@ -207,7 +209,7 @@
207209
208210
@param mapper The mapper operation performing the mapping.
209211
@param mappingOperation The mapping operation that has finished.
210-
@param keyPath The key path that was mapped. A `nil` key path indicates that the mapping matched the entire `sourceObject`.
212+
@param keyPath The key path that was mapped. A `nil` key path indicates that the mapping matched the entire `representation`.
211213
*/
212214
- (void)mapper:(RKMapperOperation *)mapper didFinishMappingOperation:(RKMappingOperation *)mappingOperation forKeyPath:(NSString *)keyPath;
213215

@@ -216,7 +218,7 @@
216218
217219
@param mapper The mapper operation performing the mapping.
218220
@param mappingOperation The mapping operation that has failed.
219-
@param keyPath The key path that was mapped. A `nil` key path indicates that the mapping matched the entire `sourceObject`.
221+
@param keyPath The key path that was mapped. A `nil` key path indicates that the mapping matched the entire `representation`.
220222
@param error The error that occurred during the execution of the mapping operation.
221223
*/
222224
- (void)mapper:(RKMapperOperation *)mapper didFailMappingOperation:(RKMappingOperation *)mappingOperation forKeyPath:(NSString *)keyPath withError:(NSError *)error;

0 commit comments

Comments
 (0)