Skip to content

Commit 280fd5d

Browse files
committed
Fix crashes during refetch if objects were deleted. Expand managed object deletion support to handle nested objects. refs RestKit#1066
1 parent 1289e76 commit 280fd5d

File tree

1 file changed

+36
-10
lines changed

1 file changed

+36
-10
lines changed

Code/Network/RKManagedObjectRequestOperation.m

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ - (void)visitMapping:(RKMapping *)mapping atKeyPath:(NSString *)keyPath
115115
NSMutableArray *newValue = [[NSMutableArray alloc] initWithCapacity:[value count]];
116116
for (__strong id object in value) {
117117
if ([object isKindOfClass:[NSManagedObject class]]) {
118+
if (![object managedObjectContext]) continue; // Object was deleted
118119
object = [managedObjectContext existingObjectWithID:[object objectID] error:&error];
119120
NSCAssert(object, @"Failed to find existing object with ID %@ in context %@: %@", [object objectID], managedObjectContext, error);
120121
}
@@ -127,6 +128,7 @@ - (void)visitMapping:(RKMapping *)mapping atKeyPath:(NSString *)keyPath
127128
NSMutableSet *newValue = [[NSMutableSet alloc] initWithCapacity:[value count]];
128129
for (__strong id object in value) {
129130
if ([object isKindOfClass:[NSManagedObject class]]) {
131+
if (![object managedObjectContext]) continue; // Object was deleted
130132
object = [managedObjectContext existingObjectWithID:[object objectID] error:&error];
131133
NSCAssert(object, @"Failed to find existing object with ID %@ in context %@: %@", [object objectID], managedObjectContext, error);
132134
}
@@ -139,15 +141,21 @@ - (void)visitMapping:(RKMapping *)mapping atKeyPath:(NSString *)keyPath
139141
NSMutableOrderedSet *newValue = [NSMutableOrderedSet orderedSet];
140142
[(NSOrderedSet *)value enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
141143
if ([object isKindOfClass:[NSManagedObject class]]) {
142-
object = [managedObjectContext existingObjectWithID:[object objectID] error:&error];
143-
NSCAssert(object, @"Failed to find existing object with ID %@ in context %@: %@", [object objectID], managedObjectContext, error);
144+
if ([object managedObjectContext]) {
145+
object = [managedObjectContext existingObjectWithID:[object objectID] error:&error];
146+
NSCAssert(object, @"Failed to find existing object with ID %@ in context %@: %@", [object objectID], managedObjectContext, error);
147+
} else {
148+
// Object was deleted
149+
object = nil;
150+
}
144151
}
145152

146-
[newValue setObject:object atIndex:index];
153+
if (object) [newValue setObject:object atIndex:index];
147154
}];
148155
value = (isMutable) ? newValue : [newValue copy];
149156
} else if ([value isKindOfClass:[NSManagedObject class]]) {
150-
value = [managedObjectContext existingObjectWithID:[value objectID] error:&error];
157+
// Object becomes nil if deleted
158+
value = [value managedObjectContext] ? [managedObjectContext existingObjectWithID:[value objectID] error:&error] : nil;
151159
NSCAssert(value, @"Failed to find existing object with ID %@ in context %@: %@", [value objectID], managedObjectContext, error);
152160
}
153161

@@ -330,7 +338,7 @@ - (NSSet *)localObjectsFromFetchRequestsMatchingRequestURL:(NSError **)error
330338
return localObjects;
331339
}
332340

333-
- (BOOL)deleteLocalObjectsMissingFromMappingResult:(RKMappingResult *)result error:(NSError **)error
341+
- (BOOL)deleteLocalObjectsMissingFromMappingResult:(RKMappingResult *)result atKeyPaths:(NSSet *)keyPaths error:(NSError **)error
334342
{
335343
if (! self.deletesOrphanedObjects) {
336344
RKLogDebug(@"Skipping deletion of orphaned objects: deletesOrphanedObjects=NO");
@@ -342,11 +350,26 @@ - (BOOL)deleteLocalObjectsMissingFromMappingResult:(RKMappingResult *)result err
342350
return YES;
343351
}
344352

345-
NSArray *results = [result array];
353+
// Build an aggregate collection of all the managed objects in the mapping result
354+
NSMutableSet *managedObjectsInMappingResult = [NSMutableSet set];
355+
NSDictionary *mappingResultDictionary = result.dictionary;
356+
for (NSString *keyPath in keyPaths) {
357+
id managedObjects = [mappingResultDictionary valueForKeyPath:keyPath];
358+
if ([managedObjects isKindOfClass:[NSManagedObject class]]) {
359+
[managedObjectsInMappingResult addObject:managedObjects];
360+
} else if ([managedObjects isKindOfClass:[NSSet class]]) {
361+
[managedObjectsInMappingResult unionSet:managedObjects];
362+
} else if ([managedObjects isKindOfClass:[NSArray class]]) {
363+
[managedObjectsInMappingResult addObjectsFromArray:managedObjects];
364+
} else {
365+
[NSException raise:NSInternalInconsistencyException format:@"Unexpected object type '%@' encountered at keyPath '%@': Expected an `NSManagedObject`, `NSArray`, or `NSSet`.", [managedObjects class], keyPath];
366+
}
367+
}
368+
346369
NSSet *localObjects = [self localObjectsFromFetchRequestsMatchingRequestURL:error];
347370
if (! localObjects) return NO;
348371
for (id object in localObjects) {
349-
if (NO == [results containsObject:object]) {
372+
if (NO == [managedObjectsInMappingResult containsObject:object]) {
350373
RKLogDebug(@"Deleting orphaned object %@: not found in result set and expected at this URL", object);
351374
[self.privateContext performBlockAndWait:^{
352375
[self.privateContext deleteObject:object];
@@ -407,14 +430,18 @@ - (void)willFinish
407430
BOOL success;
408431
NSError *error = nil;
409432

433+
// Construct a set of key paths to all of the managed objects in the mapping result
434+
RKNestedManagedObjectKeyPathMappingGraphVisitor *visitor = [[RKNestedManagedObjectKeyPathMappingGraphVisitor alloc] initWithResponseDescriptors:self.responseDescriptors];
435+
NSSet *managedObjectMappingResultKeyPaths = visitor.keyPaths;
436+
410437
// Handle any cleanup
411438
success = [self deleteTargetObjectIfAppropriate:&error];
412439
if (! success) {
413440
self.error = error;
414441
return;
415442
}
416443

417-
success = [self deleteLocalObjectsMissingFromMappingResult:self.mappingResult error:&error];
444+
success = [self deleteLocalObjectsMissingFromMappingResult:self.mappingResult atKeyPaths:managedObjectMappingResultKeyPaths error:&error];
418445
if (! success) {
419446
self.error = error;
420447
return;
@@ -431,8 +458,7 @@ - (void)willFinish
431458

432459
// Refetch all managed objects nested at key paths within the results dictionary before returning
433460
if (self.mappingResult) {
434-
RKNestedManagedObjectKeyPathMappingGraphVisitor *visitor = [[RKNestedManagedObjectKeyPathMappingGraphVisitor alloc] initWithResponseDescriptors:self.responseDescriptors];
435-
NSDictionary *resultsDictionaryFromOriginalContext = RKDictionaryFromDictionaryWithManagedObjectsAtKeyPathsRefetchedInContext([self.mappingResult dictionary], visitor.keyPaths, self.managedObjectContext);
461+
NSDictionary *resultsDictionaryFromOriginalContext = RKDictionaryFromDictionaryWithManagedObjectsAtKeyPathsRefetchedInContext([self.mappingResult dictionary], managedObjectMappingResultKeyPaths, self.managedObjectContext);
436462
self.mappingResult = [[RKMappingResult alloc] initWithDictionary:resultsDictionaryFromOriginalContext];
437463
}
438464
}

0 commit comments

Comments
 (0)