Permalink
Browse files

Merge branch 'release/0.20.0-pre5'

  • Loading branch information...
2 parents 3c84765 + baa9f62 commit 9103b9b7df1738fa214915e61d29c61d92c90cc3 @blakewatters blakewatters committed Dec 27, 2012
Showing with 1,905 additions and 540 deletions.
  1. +15 −3 Code/CoreData/RKConnectionDescription.h
  2. +4 −3 Code/CoreData/RKConnectionDescription.m
  3. +1 −1 Code/CoreData/RKEntityByAttributeCache.h
  4. +1 −1 Code/CoreData/RKEntityCache.h
  5. +0 −2 Code/CoreData/RKEntityCache.m
  6. +2 −2 Code/CoreData/RKEntityMapping.h
  7. +26 −23 Code/CoreData/RKEntityMapping.m
  8. +1 −1 Code/CoreData/RKInMemoryManagedObjectCache.h
  9. +2 −2 Code/CoreData/RKManagedObjectImporter.h
  10. +1 −1 Code/CoreData/RKManagedObjectImporter.m
  11. +1 −1 Code/CoreData/RKManagedObjectMappingOperationDataSource.h
  12. +70 −1 Code/CoreData/RKManagedObjectMappingOperationDataSource.m
  13. +31 −8 Code/CoreData/RKManagedObjectStore.h
  14. +19 −4 Code/CoreData/RKManagedObjectStore.m
  15. +1 −1 Code/CoreData/RKPropertyInspector+CoreData.h
  16. +38 −16 Code/CoreData/RKPropertyInspector+CoreData.m
  17. +3 −3 Code/CoreData/RKRelationshipConnectionOperation.h
  18. +28 −11 Code/CoreData/RKRelationshipConnectionOperation.m
  19. +22 −5 Code/Network/RKHTTPRequestOperation.m
  20. +62 −48 Code/Network/RKManagedObjectRequestOperation.m
  21. +8 −4 Code/Network/RKObjectManager.h
  22. +32 −3 Code/Network/RKObjectManager.m
  23. +2 −2 Code/Network/RKObjectRequestOperation.h
  24. +3 −3 Code/Network/RKPaginator.h
  25. +3 −3 Code/Network/RKRequestDescriptor.h
  26. +1 −1 Code/Network/RKRequestDescriptor.m
  27. +4 −4 Code/Network/RKResponseDescriptor.h
  28. +4 −4 Code/Network/RKResponseDescriptor.m
  29. +5 −5 Code/Network/RKResponseMapperOperation.h
  30. +3 −3 Code/Network/RKResponseMapperOperation.m
  31. +3 −3 Code/Network/RKRoute.h
  32. +3 −3 Code/Network/RKRoute.m
  33. +1 −1 Code/Network/RKRouter.h
  34. +2 −2 Code/ObjectMapping/RKAttributeMapping.h
  35. +1 −2 Code/ObjectMapping/RKAttributeMapping.m
  36. +0 −4 Code/ObjectMapping/RKErrorMessage.h
  37. +1 −2 Code/ObjectMapping/RKErrorMessage.m
  38. +18 −14 Code/ObjectMapping/RKMapperOperation.h
  39. +66 −44 Code/ObjectMapping/RKMapperOperation.m
  40. +4 −4 Code/ObjectMapping/RKMapperOperation_Private.h
  41. +2 −1 Code/ObjectMapping/RKMappingErrors.h
  42. +1 −1 Code/ObjectMapping/RKMappingOperation.h
  43. +68 −16 Code/ObjectMapping/RKMappingOperation.m
  44. +11 −1 Code/ObjectMapping/RKMappingOperationDataSource.h
  45. +1 −1 Code/ObjectMapping/RKMappingResult.h
  46. +9 −8 Code/ObjectMapping/RKObjectMapping.h
  47. +80 −35 Code/ObjectMapping/RKObjectMapping.m
  48. +40 −8 Code/ObjectMapping/RKPropertyInspector.h
  49. +44 −21 Code/ObjectMapping/RKPropertyInspector.m
  50. +36 −2 Code/ObjectMapping/RKRelationshipMapping.h
  51. +11 −1 Code/ObjectMapping/RKRelationshipMapping.m
  52. +1 −0 Code/Support.h
  53. +3 −3 Code/Support/RKDictionaryUtilities.m
  54. +1 −1 Code/Support/RKDotNetDateFormatter.h
  55. +2 −2 Code/Support/RKDotNetDateFormatter.m
  56. +4 −19 Code/Support/RKMIMETypeSerialization.m
  57. +2 −2 Code/Support/RKPathMatcher.h
  58. +4 −4 Code/Support/RKPathMatcher.m
  59. +2 −2 Code/Testing/RKConnectionTestExpectation.h
  60. +2 −2 Code/Testing/RKConnectionTestExpectation.m
  61. +2 −2 Code/Testing/RKMappingTest.h
  62. +1 −1 Code/Testing/RKMappingTest.m
  63. +4 −4 Code/Testing/RKPropertyMappingTestExpectation.h
  64. +4 −4 Code/Testing/RKPropertyMappingTestExpectation.m
  65. +34 −32 README.md
  66. +1 −1 RestKit.podspec
  67. +10 −10 RestKit.xcodeproj/project.pbxproj
  68. +1 −1 Tests/Fixtures/JSON/humans/1.json
  69. +39 −3 Tests/Logic/CoreData/RKEntityMappingTest.m
  70. +63 −3 Tests/Logic/CoreData/RKManagedObjectMappingOperationDataSourceTest.m
  71. +66 −0 Tests/Logic/CoreData/RKManagedObjectStoreTest.m
  72. +159 −0 Tests/Logic/CoreData/RKRelationshipConnectionOperationTest.m
  73. +189 −1 Tests/Logic/Network/RKManagedObjectRequestOperationTest.m
  74. +1 −1 Tests/Logic/Network/RKObjectRequestOperationTest.m
  75. +29 −0 Tests/Logic/Network/RKResponseMapperOperationTest.m
  76. +2 −2 Tests/Logic/ObjectMapping/RKMappingOperationTest.m
  77. +22 −0 Tests/Logic/ObjectMapping/RKObjectManagerTest.m
  78. +427 −95 Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m
  79. +14 −0 Tests/Logic/ObjectMapping/RKObjectMappingTest.m
  80. BIN Tests/Models/Data Model.xcdatamodel/elements
  81. BIN Tests/Models/Data Model.xcdatamodel/layout
  82. +2 −0 Tests/Models/RKChild.h
  83. +2 −0 Tests/Models/RKChild.m
  84. +7 −0 Tests/Models/RKTestUser.h
  85. +3 −0 Tests/Models/RKTestUser.m
  86. +6 −1 Tests/Server/server.rb
  87. +1 −1 VERSION
View
18 Code/CoreData/RKConnectionDescription.h
@@ -88,7 +88,7 @@
@param sourceToDestinationEntityAttributes A dictionary specifying how attributes on the source entity correspond to attributes on the destination entity.
@return The receiver, initialized with the given relationship and attributes.
*/
-- (id)initWithRelationship:(NSRelationshipDescription *)relationship attributes:(NSDictionary *)sourceToDestinationEntityAttributes;
+- (instancetype)initWithRelationship:(NSRelationshipDescription *)relationship attributes:(NSDictionary *)sourceToDestinationEntityAttributes;
/**
The dictionary of attributes specifying how attributes on the source entity for the relationship correspond to attributes on the destination entity.
@@ -115,7 +115,7 @@
@param keyPath The key path from which to read the value that is to be set for the relationship.
@return The receiver, initialized with the given relationship and key path.
*/
-- (id)initWithRelationship:(NSRelationshipDescription *)relationship keyPath:(NSString *)keyPath;
+- (instancetype)initWithRelationship:(NSRelationshipDescription *)relationship keyPath:(NSString *)keyPath;
/**
The key path that is to be evaluated to obtain the value for the relationship.
@@ -145,8 +145,20 @@
///----------------------------
/**
+ Returns a Boolean value that determines if the connection includes subentities. If `NO`, then the connection will only be established to objects of exactly the entity specified by the relationship's entity. If `YES`, then the connection will be established to all objects of the relationship's entity and all subentities.
+
+ **Default**: `YES`
+ */
+@property (nonatomic, assign) BOOL includesSubentities;
+
+/**
+ An optional predicate for conditionally evaluating the connection based on the state of the source object.
+ */
+@property (nonatomic, strong) NSPredicate *sourcePredicate;
+
+/**
An optional predicate for filtering objects to be connected.
*/
-@property (nonatomic, copy) NSPredicate *predicate;
+@property (nonatomic, copy) NSPredicate *destinationPredicate;
@end
View
7 Code/CoreData/RKConnectionDescription.m
@@ -44,7 +44,7 @@ @interface RKConnectionDescription ()
@implementation RKConnectionDescription
-- (id)initWithRelationship:(NSRelationshipDescription *)relationship attributes:(NSDictionary *)attributes
+- (instancetype)initWithRelationship:(NSRelationshipDescription *)relationship attributes:(NSDictionary *)attributes
{
NSParameterAssert(relationship);
NSParameterAssert(attributes);
@@ -58,11 +58,12 @@ - (id)initWithRelationship:(NSRelationshipDescription *)relationship attributes:
if (self) {
self.relationship = relationship;
self.attributes = attributes;
+ self.includesSubentities = YES;
}
return self;
}
-- (id)initWithRelationship:(NSRelationshipDescription *)relationship keyPath:(NSString *)keyPath
+- (instancetype)initWithRelationship:(NSRelationshipDescription *)relationship keyPath:(NSString *)keyPath
{
NSParameterAssert(relationship);
NSParameterAssert(keyPath);
@@ -79,7 +80,7 @@ - (id)init
if ([self class] == [RKConnectionDescription class]) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"%@ Failed to call designated initializer. "
- "Invoke initWithRelationship:sourceKeyPath:destinationKeyPath:matcher: instead.",
+ "Invoke initWithRelationship:attributes: instead.",
NSStringFromClass([self class])]
userInfo:nil];
}
View
2 Code/CoreData/RKEntityByAttributeCache.h
@@ -46,7 +46,7 @@
@return The receiver, initialized with the given entity, attribute, and managed object
context.
*/
-- (id)initWithEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributeNames managedObjectContext:(NSManagedObjectContext *)context;
+- (instancetype)initWithEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributeNames managedObjectContext:(NSManagedObjectContext *)context;
///-----------------------------
/// @name Getting Cache Identity
View
2 Code/CoreData/RKEntityCache.h
@@ -43,7 +43,7 @@
@param context The managed object context containing objects to be cached.
@returns self, initialized with context.
*/
-- (id)initWithManagedObjectContext:(NSManagedObjectContext *)context;
+- (instancetype)initWithManagedObjectContext:(NSManagedObjectContext *)context;
/**
The managed object context with which the receiver is associated.
View
2 Code/CoreData/RKEntityCache.m
@@ -27,7 +27,6 @@ @interface RKEntityCache ()
@implementation RKEntityCache
-
- (id)initWithManagedObjectContext:(NSManagedObjectContext *)context
{
NSAssert(context, @"Cannot initialize entity cache with a nil context");
@@ -45,7 +44,6 @@ - (id)init
return [self initWithManagedObjectContext:nil];
}
-
- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttributes:(NSArray *)attributeNames
{
NSParameterAssert(entity);
View
4 Code/CoreData/RKEntityMapping.h
@@ -68,7 +68,7 @@
@param entity An entity with which to initialize the receiver.
@returns The receiver, initialized with the given entity.
*/
-- (id)initWithEntity:(NSEntityDescription *)entity;
+- (instancetype)initWithEntity:(NSEntityDescription *)entity;
/**
A convenience initializer that creates and returns an entity mapping for the entity with the given name in
@@ -83,7 +83,7 @@
@param managedObjectStore A managed object store containing the managed object model in which an entity with the given name is defined.
@return A new entity mapping for the entity with the given name in the managed object model of the given managed object store.
*/
-+ (id)mappingForEntityForName:(NSString *)entityName inManagedObjectStore:(RKManagedObjectStore *)managedObjectStore;
++ (instancetype)mappingForEntityForName:(NSString *)entityName inManagedObjectStore:(RKManagedObjectStore *)managedObjectStore;
///---------------------------
/// @name Accessing the Entity
View
49 Code/CoreData/RKEntityMapping.m
@@ -36,24 +36,27 @@
static NSArray *RKEntityIdentificationAttributesFromUserInfoOfEntity(NSEntityDescription *entity)
{
- id userInfoValue = [[entity userInfo] valueForKey:RKEntityIdentificationAttributesUserInfoKey];
- if (userInfoValue) {
- NSArray *attributeNames = [userInfoValue isKindOfClass:[NSArray class]] ? userInfoValue : @[ userInfoValue ];
- NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:[attributeNames count]];
- [attributeNames enumerateObjectsUsingBlock:^(NSString *attributeName, NSUInteger idx, BOOL *stop) {
- if (! [attributeName isKindOfClass:[NSString class]]) {
- [NSException raise:NSInvalidArgumentException format:@"Invalid value given in user info key '%@' of entity '%@': expected an `NSString` or `NSArray` of strings, instead got '%@' (%@)", RKEntityIdentificationAttributesUserInfoKey, [entity name], attributeName, [attributeName class]];
- }
-
- NSAttributeDescription *attribute = [[entity attributesByName] valueForKey:attributeName];
- if (! attribute) {
- [NSException raise:NSInvalidArgumentException format:@"Invalid identifier attribute specified in user info key '%@' of entity '%@': no attribue was found with the name '%@'", RKEntityIdentificationAttributesUserInfoKey, [entity name], attributeName];
- }
-
- [attributes addObject:attribute];
- }];
- return attributes;
- }
+ do {
+ id userInfoValue = [[entity userInfo] valueForKey:RKEntityIdentificationAttributesUserInfoKey];
+ if (userInfoValue) {
+ NSArray *attributeNames = [userInfoValue isKindOfClass:[NSArray class]] ? userInfoValue : @[ userInfoValue ];
+ NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:[attributeNames count]];
+ [attributeNames enumerateObjectsUsingBlock:^(NSString *attributeName, NSUInteger idx, BOOL *stop) {
+ if (! [attributeName isKindOfClass:[NSString class]]) {
+ [NSException raise:NSInvalidArgumentException format:@"Invalid value given in user info key '%@' of entity '%@': expected an `NSString` or `NSArray` of strings, instead got '%@' (%@)", RKEntityIdentificationAttributesUserInfoKey, [entity name], attributeName, [attributeName class]];
+ }
+
+ NSAttributeDescription *attribute = [[entity attributesByName] valueForKey:attributeName];
+ if (! attribute) {
+ [NSException raise:NSInvalidArgumentException format:@"Invalid identifier attribute specified in user info key '%@' of entity '%@': no attribue was found with the name '%@'", RKEntityIdentificationAttributesUserInfoKey, [entity name], attributeName];
+ }
+
+ [attributes addObject:attribute];
+ }];
+ return attributes;
+ }
+ entity = [entity superentity];
+ } while (entity);
return nil;
}
@@ -137,20 +140,20 @@ @implementation RKEntityMapping
@synthesize identificationAttributes = _identificationAttributes;
-+ (id)mappingForClass:(Class)objectClass
++ (instancetype)mappingForClass:(Class)objectClass
{
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"You must provide a managedObjectStore. Invoke mappingForClass:inManagedObjectStore: instead."]
userInfo:nil];
}
-+ (id)mappingForEntityForName:(NSString *)entityName inManagedObjectStore:(RKManagedObjectStore *)managedObjectStore
++ (instancetype)mappingForEntityForName:(NSString *)entityName inManagedObjectStore:(RKManagedObjectStore *)managedObjectStore
{
NSEntityDescription *entity = [[managedObjectStore.managedObjectModel entitiesByName] objectForKey:entityName];
return [[self alloc] initWithEntity:entity];
}
-- (id)initWithEntity:(NSEntityDescription *)entity
+- (instancetype)initWithEntity:(NSEntityDescription *)entity
{
NSAssert(entity, @"Cannot initialize an RKEntityMapping without an entity. Maybe you want RKObjectMapping instead?");
Class objectClass = NSClassFromString([entity managedObjectClassName]);
@@ -164,7 +167,7 @@ - (id)initWithEntity:(NSEntityDescription *)entity
return self;
}
-- (id)initWithClass:(Class)objectClass
+- (instancetype)initWithClass:(Class)objectClass
{
self = [super initWithClass:objectClass];
if (self) {
@@ -275,7 +278,7 @@ - (Class)classForKeyPath:(NSString *)keyPath
NSArray *components = [keyPath componentsSeparatedByString:@"."];
Class propertyClass = self.objectClass;
for (NSString *property in components) {
- propertyClass = [[RKPropertyInspector sharedInspector] classForPropertyNamed:property ofClass:propertyClass];
+ propertyClass = [[RKPropertyInspector sharedInspector] classForPropertyNamed:property ofClass:propertyClass isPrimitive:nil];
if (! propertyClass) propertyClass = [[RKPropertyInspector sharedInspector] classForPropertyNamed:property ofEntity:self.entity];
if (! propertyClass) break;
}
View
2 Code/CoreData/RKInMemoryManagedObjectCache.h
@@ -35,6 +35,6 @@
@param managedObjectContext The managed object context with which to initialize the receiver.
@return The receiver, initialized with the given managed object context.
*/
-- (id)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
+- (instancetype)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
@end
View
4 Code/CoreData/RKManagedObjectImporter.h
@@ -55,7 +55,7 @@
@warning As this initialization code path is typical for generating seed databases, the value of
`resetsStoreBeforeImporting` is initialized to **YES**.
*/
-- (id)initWithManagedObjectModel:(NSManagedObjectModel *)managedObjectModel storePath:(NSString *)storePath;
+- (instancetype)initWithManagedObjectModel:(NSManagedObjectModel *)managedObjectModel storePath:(NSString *)storePath;
/**
Initializes the receiver with a given persistent store in which to persist imported managed objects.
@@ -69,7 +69,7 @@
managed object model are determined from the given persistent store and a new managed object context with
the private queue concurrency type is constructed.
*/
-- (id)initWithPersistentStore:(NSPersistentStore *)persistentStore;
+- (instancetype)initWithPersistentStore:(NSPersistentStore *)persistentStore;
/**
A Boolean value indicating whether existing managed objects in the persistent store should
View
2 Code/CoreData/RKManagedObjectImporter.m
@@ -213,7 +213,7 @@ - (NSUInteger)importObjectsFromFileAtPath:(NSString *)path withMapping:(RKMappin
}
NSDictionary *mappingDictionary = @{ (keyPath ?: [NSNull null]) : mapping };
- RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:parsedData mappingsDictionary:mappingDictionary];
+ RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData mappingsDictionary:mappingDictionary];
mapper.mappingOperationDataSource = self.mappingOperationDataSource;
__block RKMappingResult *mappingResult;
[self.managedObjectContext performBlockAndWait:^{
View
2 Code/CoreData/RKManagedObjectMappingOperationDataSource.h
@@ -42,7 +42,7 @@
@param managedObjectCache The managed object cache used by the receiver to find existing object instances by their identification attributes.
@return The receiver, initialized with the given managed object context and managed objet cache.
*/
-- (id)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext cache:(id<RKManagedObjectCaching>)managedObjectCache;
+- (instancetype)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext cache:(id<RKManagedObjectCaching>)managedObjectCache;
///-----------------------------------------------------
/// @name Accessing the Managed Object Context and Cache
View
71 Code/CoreData/RKManagedObjectMappingOperationDataSource.m
@@ -29,6 +29,8 @@
#import "RKRelationshipConnectionOperation.h"
#import "RKMappingErrors.h"
#import "RKValueTransformers.h"
+#import "RKRelationshipMapping.h"
+#import "RKObjectUtilities.h"
extern NSString * const RKObjectMappingNestingAttributeKeyName;
@@ -128,19 +130,29 @@ @interface RKManagedObjectMappingOperationDataSource ()
@implementation RKManagedObjectMappingOperationDataSource
-- (id)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext cache:(id<RKManagedObjectCaching>)managedObjectCache
+- (instancetype)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext cache:(id<RKManagedObjectCaching>)managedObjectCache
{
NSParameterAssert(managedObjectContext);
self = [self init];
if (self) {
self.managedObjectContext = managedObjectContext;
self.managedObjectCache = managedObjectCache;
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(updateCacheWithChangesFromContextWillSaveNotification:)
+ name:NSManagedObjectContextWillSaveNotification
+ object:managedObjectContext];
}
return self;
}
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
- (id)mappingOperation:(RKMappingOperation *)mappingOperation targetObjectForRepresentation:(NSDictionary *)representation withMapping:(RKObjectMapping *)mapping
{
NSAssert(representation, @"Mappable data cannot be nil");
@@ -239,4 +251,61 @@ - (BOOL)commitChangesForMappingOperation:(RKMappingOperation *)mappingOperation
return YES;
}
+// NOTE: In theory we should be able to use the userInfo dictionary, but the dictionary was coming in empty (12/18/2012)
+- (void)updateCacheWithChangesFromContextWillSaveNotification:(NSNotification *)notification
+{
+ NSSet *objectsToAdd = [[self.managedObjectContext insertedObjects] setByAddingObjectsFromSet:[self.managedObjectContext updatedObjects]];
+
+ __block BOOL success;
+ __block NSError *error = nil;
+ [self.managedObjectContext performBlockAndWait:^{
+ success = [self.managedObjectContext obtainPermanentIDsForObjects:[objectsToAdd allObjects] error:&error];
+ }];
+
+ if (! success) {
+ 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]);
+ RKLogError(@"Obtaining permanent managed object IDs failed with error: %@", error);
+ return;
+ }
+
+ // Update the cache
+ if ([self.managedObjectCache respondsToSelector:@selector(didFetchObject:)]) {
+ for (NSManagedObject *managedObject in objectsToAdd) {
+ [self.managedObjectCache didFetchObject:managedObject];
+ }
+ }
+
+ if ([self.managedObjectCache respondsToSelector:@selector(didDeleteObject::)]) {
+ for (NSManagedObject *managedObject in [self.managedObjectContext deletedObjects]) {
+ [self.managedObjectCache didDeleteObject:managedObject];
+ }
+ }
+}
+
+- (BOOL)mappingOperation:(RKMappingOperation *)mappingOperation deleteExistingValueOfRelationshipWithMapping:(RKRelationshipMapping *)relationshipMapping error:(NSError **)error
+{
+ // Validate the assignment policy
+ if (! relationshipMapping.assignmentPolicy == RKReplaceAssignmentPolicy) {
+ NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Unable to satisfy deletion request: Relationship mapping was expected to have an assignment policy of `RKReplaceAssignmentPolicy`, but did not." };
+ NSError *localError = [NSError errorWithDomain:RKErrorDomain code:RKMappingErrorInvalidAssignmentPolicy userInfo:userInfo];
+ if (error) *error = localError;
+ return NO;
+ }
+
+ // Delete any managed objects at the destination key path from the context
+ id existingValue = [mappingOperation.destinationObject valueForKeyPath:relationshipMapping.destinationKeyPath];
+ if ([existingValue isKindOfClass:[NSManagedObject class]]) {
+ [self.managedObjectContext deleteObject:existingValue];
+ } else {
+ if (RKObjectIsCollection(existingValue)) {
+ for (NSManagedObject *managedObject in existingValue) {
+ if (! [managedObject isKindOfClass:[NSManagedObject class]]) continue;
+ [self.managedObjectContext deleteObject:managedObject];
+ }
+ }
+ }
+
+ return YES;
+}
+
@end
View
39 Code/CoreData/RKManagedObjectStore.h
@@ -38,9 +38,6 @@
The managed object context hierarchy is designed to isolate the main thread from disk I/O and avoid deadlocks. Because the primary context manages its own private queue, saving the main queue context will not result in the objects being saved to the persistent store. The primary context must be saved as well for objects to be persisted to disk.
It is also worth noting that because of the parent/child context hierarchy, objects created on the main thread will not obtain permanent managed object ID's even after the primary context has been saved. If you need to refer to the permanent representations of objects created on the main thread after a save, you may ask the main queue context to obtain permanent managed objects for your objects via `obtainPermanentIDsForObjects:error:`. Be warned that when obtaining permanent managed object ID's, you must include all newly created objects that are reachable from the object you are concerned with in the set of objects provided to `obtainPermanentIDsForObjects:error:`. This means any newly created object in a one-to-one or one-to-many relationship must be provided or you will face a crash from the managed object context. This is due to a bug in Core Data still present in iOS5, but fixed in iOS6 (see Open Radar http://openradar.appspot.com/11478919).
-
- @see `NSManagedObjectContext (RKAdditions)`
- @see `NSEntityDescription (RKAdditions)`
*/
@interface RKManagedObjectStore : NSObject
@@ -53,7 +50,7 @@
@return The default managed object store.
*/
-+ (RKManagedObjectStore *)defaultStore;
++ (instancetype)defaultStore;
/**
Sets the default managed object store for the application.
@@ -79,7 +76,7 @@
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
*/
-- (id)initWithManagedObjectModel:(NSManagedObjectModel *)managedObjectModel;
+- (instancetype)initWithManagedObjectModel:(NSManagedObjectModel *)managedObjectModel;
/**
Initializes the receiver with an existing persistent store coordinator.
@@ -91,7 +88,7 @@
@param persistentStoreCoordinator The persistent store coordinator with which to initialize the receiver.
@return The receiver, initialized with the managed object model of the given persistent store coordinator and the persistent store coordinator.
*/
-- (id)initWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)persistentStoreCoordinator;
+- (instancetype)initWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)persistentStoreCoordinator;
/**
Initializes the receiver with a managed object model obtained by merging the models from all of the application's non-framework bundles.
@@ -101,7 +98,7 @@
@warning Obtaining a managed object model by merging all bundles may result in an application error if versioned object models are in use.
*/
-- (id)init;
+- (instancetype)init;
///-----------------------------------------------------------------------------
/// @name Configuring Persistent Stores
@@ -143,14 +140,40 @@
error:(NSError **)error;
/**
- Resets the persistent stores in the receiver's persistent store coordinator and recreates them. If a store being reset is backed by a file on disk (such as a SQLite file), the file will be removed prior to recreating the store. If the store was originally created using a seed database, the seed will be recopied to reset the store to its seeded state.
+ Resets the persistent stores in the receiver's persistent store coordinator and recreates them. If a store being reset is backed by a file on disk (such as a SQLite file), the file will be removed prior to recreating the store. If the store was originally created using a seed database, the seed will be recopied to reset the store to its seeded state. If the managed object model uses External Storage for any of its entities, then the external storage directory will be recursively deleted when the store is reset.
@param error On input, a pointer to an error object. If an error occurs, this pointer is set to an actual error object containing the error information. You may specify nil for this parameter if you do not want the error information.
@return A Boolean value indicating if the reset was successful.
@bug This method will implictly result in the managed object contexts associated with the receiver to be discarded and recreated. Any managed objects or additional child contexts associated with the store will need to be discarded or else exceptions may be raised (i.e. `NSObjectInaccessibleException`).
Also note that care must be taken to cancel/suspend all mapping operations, reset all managed object contexts, and disconnect all `NSFetchedResultController` objects that are associated with managed object contexts using the persistent stores of the receiver before attempting a reset. Failure to completely disconnect usage before calling this method is likely to result in a deadlock.
+
+ As an alternative to resetting the persistent store, you may wish to consider simply deleting all managed objects out of the managed object context. If your data set is not very large, this can be a performant operation and is significantly easier to implement correctly. An example implementation for truncating all managed objects from the store is provided below:
+
+ NSBlockOpertation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ NSManagedObjectContext *managedObjectContext = [RKManagedObjectStore defaultStore].persistentStoreManagedObjectContext;
+ [managedObjectContext performBlockAndWait:^{
+ NSError *error = nil;
+ for (NSEntityDescription *entity in [RKManagedObjectStore defaultStore].managedObjectModel) {
+ NSFetchRequest *fetchRequest = [NSFetchRequest new];
+ [fetchRequest setEntity:entity];
+ [fetchRequest setIncludesSubentities:NO];
+ NSArray *objects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
+ if (! objects) RKLogWarning(@"Failed execution of fetch request %@: %@", fetchRequest, error);
+ for (NSManagedObject *managedObject in objects) {
+ [managedObjectContext deleteObject:managedObjectContext];
+ }
+ }
+
+ BOOL success = [managedObjectContext save:&error];
+ if (!success) RKLogWarning(@"Failed saving managed object context: %@", error);
+ }];
+ }];
+ [operation setCompletionBlock:^{
+ // Do stuff once the truncation is complete
+ }];
+ [operation start];
*/
- (BOOL)resetPersistentStores:(NSError **)error;
View
23 Code/CoreData/RKManagedObjectStore.m
@@ -45,8 +45,7 @@ @interface RKManagedObjectStore ()
@implementation RKManagedObjectStore
-
-+ (RKManagedObjectStore *)defaultStore
++ (instancetype)defaultStore
{
return defaultStore;
}
@@ -58,7 +57,7 @@ + (void)setDefaultStore:(RKManagedObjectStore *)managedObjectStore
}
}
-- (id)initWithManagedObjectModel:(NSManagedObjectModel *)managedObjectModel
+- (instancetype)initWithManagedObjectModel:(NSManagedObjectModel *)managedObjectModel
{
self = [super init];
if (self) {
@@ -74,7 +73,7 @@ - (id)initWithManagedObjectModel:(NSManagedObjectModel *)managedObjectModel
return self;
}
-- (id)initWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)persistentStoreCoordinator
+- (instancetype)initWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
self = [self initWithManagedObjectModel:persistentStoreCoordinator.managedObjectModel];
if (self) {
@@ -226,6 +225,22 @@ - (BOOL)resetPersistentStores:(NSError **)error
if (error) *error = localError;
return NO;
}
+
+ // Check for and remove an external storage directory
+ NSString *supportDirectoryName = [NSString stringWithFormat:@".%@_SUPPORT", [[URL lastPathComponent] stringByDeletingPathExtension]];
+ NSURL *supportDirectoryFileURL = [NSURL URLWithString:supportDirectoryName relativeToURL:[URL URLByDeletingLastPathComponent]];
+ BOOL isDirectory = NO;
+ if ([[NSFileManager defaultManager] fileExistsAtPath:[supportDirectoryFileURL path] isDirectory:&isDirectory]) {
+ if (isDirectory) {
+ if (! [[NSFileManager defaultManager] removeItemAtURL:supportDirectoryFileURL error:&localError]) {
+ RKLogError(@"Failed to remove persistent store Support directory at URL %@: %@", supportDirectoryFileURL, localError);
+ if (error) *error = localError;
+ return NO;
+ }
+ } else {
+ RKLogWarning(@"Found external support item for store at path that is not a directory: %@", [supportDirectoryFileURL path]);
+ }
+ }
} else {
RKLogDebug(@"Skipped removal of persistent store file: URL for persistent store is not a file URL. (%@)", URL);
}
View
2 Code/CoreData/RKPropertyInspector+CoreData.h
@@ -31,7 +31,7 @@
@param entity The entity to retrieve the properties names and classes of.
@return A dictionary containing the names and classes of the given entity.
*/
-- (NSDictionary *)propertyNamesAndClassesForEntity:(NSEntityDescription *)entity;
+- (NSDictionary *)propertyInspectionForEntity:(NSEntityDescription *)entity;
/**
Returns the class used to represent the property with the given name on the given entity.
View
54 Code/CoreData/RKPropertyInspector+CoreData.m
@@ -31,22 +31,25 @@
@implementation RKPropertyInspector (CoreData)
-- (NSDictionary *)propertyNamesAndClassesForEntity:(NSEntityDescription *)entity
+- (NSDictionary *)propertyInspectionForEntity:(NSEntityDescription *)entity
{
- NSMutableDictionary *propertyNamesAndTypes = [_propertyNamesToTypesCache objectForKey:[entity name]];
- if (propertyNamesAndTypes) {
- return propertyNamesAndTypes;
+ NSMutableDictionary *entityInspection = [_inspectionCache objectForKey:[entity name]];
+ if (entityInspection) {
+ return entityInspection;
}
- propertyNamesAndTypes = [NSMutableDictionary dictionary];
+ entityInspection = [NSMutableDictionary dictionary];
for (NSString *name in [entity attributesByName]) {
NSAttributeDescription *attributeDescription = [[entity attributesByName] valueForKey:name];
if ([attributeDescription attributeValueClassName]) {
Class cls = NSClassFromString([attributeDescription attributeValueClassName]);
if ([cls isSubclassOfClass:[NSNumber class]] && [attributeDescription attributeType] == NSBooleanAttributeType) {
cls = objc_getClass("NSCFBoolean") ?: objc_getClass("__NSCFBoolean") ?: cls;
}
- [propertyNamesAndTypes setValue:cls forKey:name];
+ NSDictionary *propertyInspection = @{ RKPropertyInspectionNameKey: name,
+ RKPropertyInspectionKeyValueCodingClassKey: cls,
+ RKPropertyInspectionIsPrimitiveKey: @(NO) };
+ [entityInspection setValue:propertyInspection forKey:name];
} else if ([attributeDescription attributeType] == NSTransformableAttributeType &&
![name isEqualToString:@"_mapkit_hasPanoramaID"]) {
@@ -60,47 +63,66 @@ - (NSDictionary *)propertyNamesAndClassesForEntity:(NSEntityDescription *)entity
const char *attr = property_getAttributes(prop);
Class destinationClass = RKKeyValueCodingClassFromPropertyAttributes(attr);
if (destinationClass) {
- [propertyNamesAndTypes setObject:destinationClass forKey:name];
+ NSDictionary *propertyInspection = @{ RKPropertyInspectionNameKey: name,
+ RKPropertyInspectionKeyValueCodingClassKey: destinationClass,
+ RKPropertyInspectionIsPrimitiveKey: @(NO) };
+ [entityInspection setObject:propertyInspection forKey:name];
}
}
}
for (NSString *name in [entity relationshipsByName]) {
NSRelationshipDescription *relationshipDescription = [[entity relationshipsByName] valueForKey:name];
if ([relationshipDescription isToMany]) {
- [propertyNamesAndTypes setValue:[NSSet class] forKey:name];
+ if ([relationshipDescription isOrdered]) {
+ NSDictionary *propertyInspection = @{ RKPropertyInspectionNameKey: name,
+ RKPropertyInspectionKeyValueCodingClassKey: [NSOrderedSet class],
+ RKPropertyInspectionIsPrimitiveKey: @(NO) };
+ [entityInspection setObject:propertyInspection forKey:name];
+ } else {
+ NSDictionary *propertyInspection = @{ RKPropertyInspectionNameKey: name,
+ RKPropertyInspectionKeyValueCodingClassKey: [NSSet class],
+ RKPropertyInspectionIsPrimitiveKey: @(NO) };
+ [entityInspection setObject:propertyInspection forKey:name];
+ }
} else {
NSEntityDescription *destinationEntity = [relationshipDescription destinationEntity];
Class destinationClass = NSClassFromString([destinationEntity managedObjectClassName]);
- [propertyNamesAndTypes setValue:destinationClass forKey:name];
+ NSDictionary *propertyInspection = @{ RKPropertyInspectionNameKey: name,
+ RKPropertyInspectionKeyValueCodingClassKey: destinationClass,
+ RKPropertyInspectionIsPrimitiveKey: @(NO) };
+ [entityInspection setObject:propertyInspection forKey:name];
}
}
- [_propertyNamesToTypesCache setObject:propertyNamesAndTypes forKey:[entity name]];
- RKLogDebug(@"Cached property names and types for Entity '%@': %@", entity, propertyNamesAndTypes);
- return propertyNamesAndTypes;
+ [_inspectionCache setObject:entityInspection forKey:[entity name]];
+ RKLogDebug(@"Cached property inspection for Entity '%@': %@", entity, entityInspection);
+ return entityInspection;
}
- (Class)classForPropertyNamed:(NSString *)propertyName ofEntity:(NSEntityDescription *)entity
{
- return [[self propertyNamesAndClassesForEntity:entity] valueForKey:propertyName];
+ NSDictionary *entityInspection = [self propertyInspectionForEntity:entity];
+ NSDictionary *propertyInspection = [entityInspection objectForKey:propertyName];
+ return [propertyInspection objectForKey:RKPropertyInspectionKeyValueCodingClassKey];
}
@end
@interface NSManagedObject (RKPropertyInspection)
-- (Class)rk_classForPropertyAtKeyPath:(NSString *)keyPath;
+- (Class)rk_classForPropertyAtKeyPath:(NSString *)keyPath isPrimitive:(BOOL *)isPrimitive;
@end
@implementation NSManagedObject (RKPropertyInspection)
-- (Class)rk_classForPropertyAtKeyPath:(NSString *)keyPath
+- (Class)rk_classForPropertyAtKeyPath:(NSString *)keyPath isPrimitive:(BOOL *)isPrimitive
{
NSArray *components = [keyPath componentsSeparatedByString:@"."];
Class propertyClass = [self class];
for (NSString *property in components) {
+ if (isPrimitive) *isPrimitive = NO; // Core Data does not enable you to model primitives
propertyClass = [[RKPropertyInspector sharedInspector] classForPropertyNamed:property ofEntity:[self entity]];
- propertyClass = propertyClass ?: [[RKPropertyInspector sharedInspector] classForPropertyNamed:property ofClass:propertyClass];
+ propertyClass = propertyClass ?: [[RKPropertyInspector sharedInspector] classForPropertyNamed:property ofClass:propertyClass isPrimitive:isPrimitive];
if (! propertyClass) break;
}
View
6 Code/CoreData/RKRelationshipConnectionOperation.h
@@ -44,9 +44,9 @@
@param managedObjectCache The managed object cache from which to attempt to fetch a matching object to satisfy the connection.
@return The receiver, initialized with the given managed object, connection mapping, and managed object cache.
*/
-- (id)initWithManagedObject:(NSManagedObject *)managedObject
- connection:(RKConnectionDescription *)connection
- managedObjectCache:(id<RKManagedObjectCaching>)managedObjectCache;
+- (instancetype)initWithManagedObject:(NSManagedObject *)managedObject
+ connection:(RKConnectionDescription *)connection
+ managedObjectCache:(id<RKManagedObjectCaching>)managedObjectCache;
///--------------------------------------------
/// @name Accessing Details About the Operation
View
39 Code/CoreData/RKRelationshipConnectionOperation.m
@@ -38,16 +38,21 @@ static id RKMutableSetValueForRelationship(NSRelationshipDescription *relationsh
return [relationship isOrdered] ? [NSMutableOrderedSet orderedSet] : [NSMutableSet set];
}
+static BOOL RKConnectionAttributeValuesIsNotConnectable(NSDictionary *attributeValues)
+{
+ return [[NSSet setWithArray:[attributeValues allValues]] isEqualToSet:[NSSet setWithObject:[NSNull null]]];
+}
+
static NSDictionary *RKConnectionAttributeValuesWithObject(RKConnectionDescription *connection, NSManagedObject *managedObject)
{
NSCAssert([connection isForeignKeyConnection], @"Only valid for a foreign key connection");
NSMutableDictionary *destinationEntityAttributeValues = [NSMutableDictionary dictionaryWithCapacity:[connection.attributes count]];
for (NSString *sourceAttribute in connection.attributes) {
NSString *destinationAttribute = [connection.attributes objectForKey:sourceAttribute];
id sourceValue = [managedObject valueForKey:sourceAttribute];
- [destinationEntityAttributeValues setValue:sourceValue forKey:destinationAttribute];
+ [destinationEntityAttributeValues setValue:sourceValue ?: [NSNull null] forKey:destinationAttribute];
}
- return destinationEntityAttributeValues;
+ return RKConnectionAttributeValuesIsNotConnectable(destinationEntityAttributeValues) ? nil : destinationEntityAttributeValues;
}
@interface RKRelationshipConnectionOperation ()
@@ -65,9 +70,9 @@ @interface RKRelationshipConnectionOperation ()
@implementation RKRelationshipConnectionOperation
-- (id)initWithManagedObject:(NSManagedObject *)managedObject
- connection:(RKConnectionDescription *)connection
- managedObjectCache:(id<RKManagedObjectCaching>)managedObjectCache;
+- (instancetype)initWithManagedObject:(NSManagedObject *)managedObject
+ connection:(RKConnectionDescription *)connection
+ managedObjectCache:(id<RKManagedObjectCaching>)managedObjectCache;
{
NSParameterAssert(managedObject);
NSAssert([managedObject isKindOfClass:[NSManagedObject class]], @"Relationship connection requires an instance of NSManagedObject");
@@ -136,15 +141,24 @@ - (id)relationshipValueWithConnectionResult:(id)result
return result;
}
-- (id)findConnected
+- (id)findConnected:(BOOL *)shouldConnectRelationship
{
+ *shouldConnectRelationship = YES;
id connectionResult = nil;
+ if (self.connection.sourcePredicate && ![self.connection.sourcePredicate evaluateWithObject:self.managedObject]) return nil;
+
if ([self.connection isForeignKeyConnection]) {
NSDictionary *attributeValues = RKConnectionAttributeValuesWithObject(self.connection, self.managedObject);
+ // If there are no attribute values available for connecting, skip the connection entirely
+ if (! attributeValues) {
+ *shouldConnectRelationship = NO;
+ return nil;
+ }
NSSet *managedObjects = [self.managedObjectCache managedObjectsWithEntity:[self.connection.relationship destinationEntity]
attributeValues:attributeValues
inManagedObjectContext:self.managedObjectContext];
- if (self.connection.predicate) managedObjects = [managedObjects filteredSetUsingPredicate:self.connection.predicate];
+ if (self.connection.destinationPredicate) managedObjects = [managedObjects filteredSetUsingPredicate:self.connection.destinationPredicate];
+ if (!self.connection.includesSubentities) managedObjects = [managedObjects filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"entity == %@", [self.connection.relationship destinationEntity]]];
if ([self.connection.relationship isToMany]) {
connectionResult = managedObjects;
} else {
@@ -170,10 +184,13 @@ - (void)main
NSString *relationshipName = self.connection.relationship.name;
RKLogTrace(@"Connecting relationship '%@' with mapping: %@", relationshipName, self.connection);
[self.managedObjectContext performBlockAndWait:^{
- self.connectedValue = [self findConnected];
- [self.managedObject setValue:self.connectedValue forKeyPath:relationshipName];
- RKLogDebug(@"Connected relationship '%@' to object '%@'", relationshipName, self.connectedValue);
- if (self.connectionBlock) self.connectionBlock(self, self.connectedValue);
+ BOOL shouldConnect = YES;
+ self.connectedValue = [self findConnected:&shouldConnect];
+ if (shouldConnect) {
+ [self.managedObject setValue:self.connectedValue forKeyPath:relationshipName];
+ RKLogDebug(@"Connected relationship '%@' to object '%@'", relationshipName, self.connectedValue);
+ if (self.connectionBlock) self.connectionBlock(self, self.connectedValue);
+ }
}];
}
View
27 Code/Network/RKHTTPRequestOperation.m
@@ -18,6 +18,7 @@
// limitations under the License.
//
+#import <objc/runtime.h>
#import "RKHTTPRequestOperation.h"
#import "RKLog.h"
#import "lcl_RK.h"
@@ -126,9 +127,17 @@ - (void)dealloc
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
+static void *RKHTTPRequestOperationStartDate = &RKHTTPRequestOperationStartDate;
+
- (void)HTTPOperationDidStart:(NSNotification *)notification
{
RKHTTPRequestOperation *operation = [notification object];
+
+ if (![operation isKindOfClass:[AFHTTPRequestOperation class]]) {
+ return;
+ }
+
+ objc_setAssociatedObject(operation, RKHTTPRequestOperationStartDate, [NSDate date], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if ((_RKlcl_component_level[(__RKlcl_log_symbol(RKlcl_cRestKitNetwork))]) >= (__RKlcl_log_symbol(RKlcl_vTrace))) {
NSString *body = nil;
@@ -147,19 +156,27 @@ - (void)HTTPOperationDidStart:(NSNotification *)notification
- (void)HTTPOperationDidFinish:(NSNotification *)notification
{
RKHTTPRequestOperation *operation = [notification object];
+
+ if (![operation isKindOfClass:[AFHTTPRequestOperation class]]) {
+ return;
+ }
+
+ NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:objc_getAssociatedObject(operation, RKHTTPRequestOperationStartDate)];
+
NSString *statusCodeString = RKStringFromStatusCode([operation.response statusCode]);
- NSString *statusCodeFragment = statusCodeString ? [NSString stringWithFormat:@"(%ld %@)", (long)[operation.response statusCode], statusCodeString] : [NSString stringWithFormat:@"(%ld)", (long)[operation.response statusCode]];
+ NSString *elapsedTimeString = [NSString stringWithFormat:@"[%.04f s]", elapsedTime];
+ NSString *statusCodeAndElapsedTime = statusCodeString ? [NSString stringWithFormat:@"(%ld %@) %@", (long)[operation.response statusCode], statusCodeString, elapsedTimeString] : [NSString stringWithFormat:@"(%ld) %@", (long)[operation.response statusCode], elapsedTimeString];
if (operation.error) {
if ((_RKlcl_component_level[(__RKlcl_log_symbol(RKlcl_cRestKitNetwork))]) >= (__RKlcl_log_symbol(RKlcl_vTrace))) {
- RKLogError(@"%@ '%@' %@:\nerror=%@\nresponse.body=%@", [operation.request HTTPMethod], [[operation.request URL] absoluteString], statusCodeFragment, operation.error, operation.responseString);
+ RKLogError(@"%@ '%@' %@:\nerror=%@\nresponse.body=%@", [operation.request HTTPMethod], [[operation.request URL] absoluteString], statusCodeAndElapsedTime, operation.error, operation.responseString);
} else {
- RKLogError(@"%@ '%@' %@: %@", [operation.request HTTPMethod], [[operation.request URL] absoluteString], statusCodeFragment, operation.error);
+ RKLogError(@"%@ '%@' %@: %@", [operation.request HTTPMethod], [[operation.request URL] absoluteString], statusCodeAndElapsedTime, operation.error);
}
} else {
if ((_RKlcl_component_level[(__RKlcl_log_symbol(RKlcl_cRestKitNetwork))]) >= (__RKlcl_log_symbol(RKlcl_vTrace))) {
- RKLogTrace(@"%@ '%@' %@:\nresponse.headers=%@\nresponse.body=%@", [operation.request HTTPMethod], [[operation.request URL] absoluteString], statusCodeFragment, [operation.response allHeaderFields], RKLogTruncateString(operation.responseString));
+ RKLogTrace(@"%@ '%@' %@:\nresponse.headers=%@\nresponse.body=%@", [operation.request HTTPMethod], [[operation.request URL] absoluteString], statusCodeAndElapsedTime, [operation.response allHeaderFields], RKLogTruncateString(operation.responseString));
} else {
- RKLogInfo(@"%@ '%@' %@", [operation.request HTTPMethod], [[operation.request URL] absoluteString], statusCodeFragment);
+ RKLogInfo(@"%@ '%@' %@", [operation.request HTTPMethod], [[operation.request URL] absoluteString], statusCodeAndElapsedTime);
}
}
}
View
110 Code/Network/RKManagedObjectRequestOperation.m
@@ -69,22 +69,18 @@ - (NSSet *)keyPaths
- (void)visitMapping:(RKMapping *)mapping atKeyPath:(NSString *)keyPath
{
id actualKeyPath = keyPath ?: [NSNull null];
- if ([self.keyPaths containsObject:actualKeyPath]) return;
-
- if ([mapping isKindOfClass:[RKEntityMapping class]]) {
- [self.mutableKeyPaths addObject:actualKeyPath];
- } else {
- if ([mapping isKindOfClass:[RKDynamicMapping class]]) {
- RKDynamicMapping *dynamicMapping = (RKDynamicMapping *)mapping;
- for (RKMapping *nestedMapping in dynamicMapping.objectMappings) {
- [self visitMapping:nestedMapping atKeyPath:keyPath];
- }
- } else if ([mapping isKindOfClass:[RKObjectMapping class]]) {
- RKObjectMapping *objectMapping = (RKObjectMapping *)mapping;
- for (RKRelationshipMapping *relationshipMapping in objectMapping.relationshipMappings) {
- NSString *nestedKeyPath = keyPath ? [@[ keyPath, relationshipMapping.destinationKeyPath ] componentsJoinedByString:@"."] : relationshipMapping.destinationKeyPath;
- [self visitMapping:relationshipMapping.mapping atKeyPath:nestedKeyPath];
- }
+ if ([self.keyPaths containsObject:actualKeyPath]) return;
+ if ([mapping isKindOfClass:[RKEntityMapping class]]) [self.mutableKeyPaths addObject:actualKeyPath];
+ if ([mapping isKindOfClass:[RKDynamicMapping class]]) {
+ RKDynamicMapping *dynamicMapping = (RKDynamicMapping *)mapping;
+ for (RKMapping *nestedMapping in dynamicMapping.objectMappings) {
+ [self visitMapping:nestedMapping atKeyPath:keyPath];
+ }
+ } else if ([mapping isKindOfClass:[RKObjectMapping class]]) {
+ RKObjectMapping *objectMapping = (RKObjectMapping *)mapping;
+ for (RKRelationshipMapping *relationshipMapping in objectMapping.relationshipMappings) {
+ NSString *nestedKeyPath = keyPath ? [@[ keyPath, relationshipMapping.destinationKeyPath ] componentsJoinedByString:@"."] : relationshipMapping.destinationKeyPath;
+ [self visitMapping:relationshipMapping.mapping atKeyPath:nestedKeyPath];
}
}
}
@@ -102,6 +98,28 @@ - (void)visitMapping:(RKMapping *)mapping atKeyPath:(NSString *)keyPath
return fetchRequests;
}
+/**
+ Returns the set of keys containing the outermost nesting keypath for all children.
+ For example, given a set containing: 'this', 'this.that', 'another.one.test', 'another.two.test', 'another.one.test.nested'
+ would return: 'this, 'another.one', 'another.two'
+ */
+NSSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths);
+NSSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths)
+{
+ return [setOfKeyPaths objectsPassingTest:^BOOL(NSString *keyPath, BOOL *stop) {
+ if ([keyPath isEqual:[NSNull null]]) return YES; // Special case the root key path
+ NSArray *keyPathComponents = [keyPath componentsSeparatedByString:@"."];
+ NSMutableSet *parentKeyPaths = [NSMutableSet set];
+ for (NSUInteger index = 0; index < [keyPathComponents count] - 1; index++) {
+ [parentKeyPaths addObject:[[keyPathComponents subarrayWithRange:NSMakeRange(0, index + 1)] componentsJoinedByString:@"."]];
+ }
+ for (NSString *parentKeyPath in parentKeyPaths) {
+ if ([setOfKeyPaths containsObject:parentKeyPath]) return NO;
+ }
+ return YES;
+ }];
+}
+
// When we map the root object, it is returned under the key `[NSNull null]`
static id RKMappedValueForKeyPathInDictionary(NSString *keyPath, NSDictionary *dictionary)
{
@@ -110,17 +128,33 @@ static id RKMappedValueForKeyPathInDictionary(NSString *keyPath, NSDictionary *d
static void RKSetMappedValueForKeyPathInDictionary(id value, NSString *keyPath, NSMutableDictionary *dictionary)
{
+ NSCParameterAssert(value);
+ NSCParameterAssert(keyPath);
+ NSCParameterAssert(dictionary);
[keyPath isEqual:[NSNull null]] ? [dictionary setObject:value forKey:keyPath] : [dictionary setValue:value forKeyPath:keyPath];
}
+// Precondition: Must be called from within the correct context
+static NSManagedObject *RKRefetchManagedObjectInContext(NSManagedObject *managedObject, NSManagedObjectContext *managedObjectContext)
+{
+ NSManagedObjectID *managedObjectID = [managedObject objectID];
+ if (! [managedObject managedObjectContext]) return nil; // Object has been deleted
+ if ([managedObjectID isTemporaryID]) {
+ RKLogWarning(@"Unable to refetch managed object %@: the object has a temporary managed object ID.", managedObject);
+ return managedObject;
+ }
+ NSError *error = nil;
+ NSManagedObject *refetchedObject = [managedObjectContext existingObjectWithID:managedObjectID error:&error];
+ NSCAssert(refetchedObject, @"Failed to find existing object with ID %@ in context %@: %@", managedObjectID, managedObjectContext, error);
+ return refetchedObject;
+}
+
// Finds the key paths for all entity mappings in the graph whose parent objects are not other managed objects
static NSDictionary *RKDictionaryFromDictionaryWithManagedObjectsAtKeyPathsRefetchedInContext(NSDictionary *dictionaryOfManagedObjects, NSSet *keyPaths, NSManagedObjectContext *managedObjectContext)
{
if (! [dictionaryOfManagedObjects count]) return dictionaryOfManagedObjects;
NSMutableDictionary *newDictionary = [dictionaryOfManagedObjects mutableCopy];
[managedObjectContext performBlockAndWait:^{
- __block NSError *error = nil;
-
for (NSString *keyPath in keyPaths) {
id value = RKMappedValueForKeyPathInDictionary(keyPath, dictionaryOfManagedObjects);
if (! value) {
@@ -129,52 +163,31 @@ static void RKSetMappedValueForKeyPathInDictionary(id value, NSString *keyPath,
BOOL isMutable = [value isKindOfClass:[NSMutableArray class]];
NSMutableArray *newValue = [[NSMutableArray alloc] initWithCapacity:[value count]];
for (__strong id object in value) {
- if ([object isKindOfClass:[NSManagedObject class]]) {
- if (![object managedObjectContext]) continue; // Object was deleted
- object = [managedObjectContext existingObjectWithID:[object objectID] error:&error];
- NSCAssert(object, @"Failed to find existing object with ID %@ in context %@: %@", [object objectID], managedObjectContext, error);
- }
-
- [newValue addObject:object];
+ if ([object isKindOfClass:[NSManagedObject class]]) object = RKRefetchManagedObjectInContext(object, managedObjectContext);
+ if (object) [newValue addObject:object];
}
value = (isMutable) ? newValue : [newValue copy];
} else if ([value isKindOfClass:[NSSet class]]) {
BOOL isMutable = [value isKindOfClass:[NSMutableSet class]];
NSMutableSet *newValue = [[NSMutableSet alloc] initWithCapacity:[value count]];
for (__strong id object in value) {
- if ([object isKindOfClass:[NSManagedObject class]]) {
- if (![object managedObjectContext]) continue; // Object was deleted
- object = [managedObjectContext existingObjectWithID:[object objectID] error:&error];
- NSCAssert(object, @"Failed to find existing object with ID %@ in context %@: %@", [object objectID], managedObjectContext, error);
- }
-
- [newValue addObject:object];
+ if ([object isKindOfClass:[NSManagedObject class]]) object = RKRefetchManagedObjectInContext(object, managedObjectContext);
+ if (object) [newValue addObject:object];
}
value = (isMutable) ? newValue : [newValue copy];
} else if ([value isKindOfClass:[NSOrderedSet class]]) {
BOOL isMutable = [value isKindOfClass:[NSMutableOrderedSet class]];
NSMutableOrderedSet *newValue = [NSMutableOrderedSet orderedSet];
[(NSOrderedSet *)value enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
- if ([object isKindOfClass:[NSManagedObject class]]) {
- if ([object managedObjectContext]) {
- object = [managedObjectContext existingObjectWithID:[object objectID] error:&error];
- NSCAssert(object, @"Failed to find existing object with ID %@ in context %@: %@", [object objectID], managedObjectContext, error);
- } else {
- // Object was deleted
- object = nil;
- }
- }
-
+ if ([object isKindOfClass:[NSManagedObject class]]) object = RKRefetchManagedObjectInContext(object, managedObjectContext);
if (object) [newValue setObject:object atIndex:index];
}];
value = (isMutable) ? newValue : [newValue copy];
} else if ([value isKindOfClass:[NSManagedObject class]]) {
- // Object becomes nil if deleted
- value = [value managedObjectContext] ? [managedObjectContext existingObjectWithID:[value objectID] error:&error] : nil;
- NSCAssert(value, @"Failed to find existing object with ID %@ in context %@: %@", [value objectID], managedObjectContext, error);
+ value = RKRefetchManagedObjectInContext(value, managedObjectContext);
}
- RKSetMappedValueForKeyPathInDictionary(value, keyPath, newDictionary);
+ if (value) RKSetMappedValueForKeyPathInDictionary(value, keyPath, newDictionary);
}
}];
@@ -187,7 +200,7 @@ static void RKSetMappedValueForKeyPathInDictionary(id value, NSString *keyPath,
NSCParameterAssert(responseDescriptors);
NSArray *baseURLs = [responseDescriptors valueForKeyPath:@"@distinctUnionOfObjects.baseURL"];
if ([baseURLs count] == 1) {
- NSURL *baseURL = baseURLs[0];
+ NSURL *baseURL = [baseURLs objectAtIndex:0];
NSString *pathAndQueryString = RKPathAndQueryStringFromURLRelativeToURL(URL, baseURL);
URL = [NSURL URLWithString:pathAndQueryString relativeToURL:baseURL];
}
@@ -483,7 +496,8 @@ - (void)willFinish
// Refetch all managed objects nested at key paths within the results dictionary before returning
if (self.mappingResult) {
- NSDictionary *resultsDictionaryFromOriginalContext = RKDictionaryFromDictionaryWithManagedObjectsAtKeyPathsRefetchedInContext([self.mappingResult dictionary], managedObjectMappingResultKeyPaths, self.managedObjectContext);
+ NSSet *nonNestedKeyPaths = RKSetByRemovingSubkeypathsFromSet(managedObjectMappingResultKeyPaths);
+ NSDictionary *resultsDictionaryFromOriginalContext = RKDictionaryFromDictionaryWithManagedObjectsAtKeyPathsRefetchedInContext([self.mappingResult dictionary], nonNestedKeyPaths, self.managedObjectContext);
self.mappingResult = [[RKMappingResult alloc] initWithDictionary:resultsDictionaryFromOriginalContext];
}
}
View
12 Code/Network/RKObjectManager.h
@@ -85,7 +85,11 @@ RKMappingResult, RKRequestDescriptor, RKResponseDescriptor;
Once a path pattern has been registered via the routing system, the manager can automatically build full request URL's when given nothing but the object to be sent.
- The second use case of path patterns is in the matching of path into a dictionary of attributes. In this case, the path pattern is evaluatd against a string and used to construct an `NSDictionary` object containing the matched key paths, optionally including the values of a query string. This functionality is provided via the `RKPathMatcher` class and is discussed in detail in the accompanying documentation.
+ The second use case of path patterns is in the matching of path into a dictionary of attributes. In this case, the path pattern is evaluatd against a string and used to construct an `NSDictionary` object containing the matched key paths, optionally including the values of a query string. This functionality is provided via the `RKPathMatcher` class and is discussed in detail in the accompanying documentation.
+
+ ### Escaping Path Patterns
+
+ Note that path patterns will by default interpret anything prefixed with a period that follows a dynamic path segment as a key path. This can cause an issue if you have a dynamic path segment that is followed by a file extension. For example, a path pattern of '/categories/:categoryID.json' would be erroneously interpretted as containing a dynamic path segment whose value is interpolated from the 'categoryID.json' key path. This key path evaluation behavior can be suppressed by escaping the period preceding the non-dynamic part of the pattern with two leading slashes, as in '/categories/:categoryID\\.json'.
## Request and Response Descriptors
@@ -227,7 +231,7 @@ RKMappingResult, RKRequestDescriptor, RKResponseDescriptor;
@return The shared manager instance.
*/
-+ (RKObjectManager *)sharedManager;
++ (instancetype)sharedManager;
/**
Set the shared instance of the object manager
@@ -248,7 +252,7 @@ RKMappingResult, RKRequestDescriptor, RKResponseDescriptor;
@param baseURL The base URL with which to initialize the `AFHTTPClient` object
@return A new `RKObjectManager` initialized with an `AFHTTPClient` that was initialized with the given baseURL.
*/
-+ (id)managerWithBaseURL:(NSURL *)baseURL;
++ (instancetype)managerWithBaseURL:(NSURL *)baseURL;
/**
Initializes the receiver with the given AFNetworking HTTP client object, adopting the network configuration from the client.
@@ -258,7 +262,7 @@ RKMappingResult, RKRequestDescriptor, RKResponseDescriptor;
@param client The AFNetworking HTTP client with which to initialize the receiver.
@return The receiver, initialized with the given client.
*/
-- (id)initWithHTTPClient:(AFHTTPClient *)client;
+- (instancetype)initWithHTTPClient:(AFHTTPClient *)client;
///------------------------------------------
/// @name Accessing Object Manager Properties
View
35 Code/Network/RKObjectManager.m
@@ -169,6 +169,34 @@ static BOOL RKDoesArrayOfResponseDescriptorsContainEntityMapping(NSArray *respon
return NO;
}
+static BOOL RKDoesArrayOfResponseDescriptorsContainMappingForClass(NSArray *responseDescriptors, Class classToBeMapped)
+{
+ // Visit all mappings accessible from the object graphs of all response descriptors
+ NSMutableSet *accessibleMappings = [NSMutableSet set];
+ for (RKResponseDescriptor *responseDescriptor in responseDescriptors) {
+ if (! [accessibleMappings containsObject:responseDescriptor.mapping]) {
+ RKMappingGraphVisitor *graphVisitor = [[RKMappingGraphVisitor alloc] initWithMapping:responseDescriptor.mapping];
+ [accessibleMappings unionSet:graphVisitor.mappings];
+ }
+ }
+
+ // Enumerate all mappings and search for a mapping matching the class
+ for (RKMapping *mapping in accessibleMappings) {
+ if ([mapping isKindOfClass:[RKObjectMapping class]]) {
+ if ([[(RKObjectMapping *)mapping objectClass] isSubclassOfClass:classToBeMapped]) return YES;
+ }
+
+ if ([mapping isKindOfClass:[RKDynamicMapping class]]) {
+ RKDynamicMapping *dynamicMapping = (RKDynamicMapping *)mapping;
+ for (RKObjectMapping *mapping in dynamicMapping.objectMappings) {
+ if ([[(RKObjectMapping *)mapping objectClass] isSubclassOfClass:classToBeMapped]) return YES;
+ }
+ }
+ }
+
+ return NO;
+}
+
static NSString *RKMIMETypeFromAFHTTPClientParameterEncoding(AFHTTPClientParameterEncoding encoding)
{
switch (encoding) {
@@ -223,7 +251,7 @@ - (id)initWithHTTPClient:(AFHTTPClient *)client
return self;
}
-+ (RKObjectManager *)sharedManager
++ (instancetype)sharedManager
{
return sharedManager;
}
@@ -381,7 +409,7 @@ - (RKManagedObjectRequestOperation *)managedObjectRequestOperationWithRequest:(N
{
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithHTTPRequestOperation:[self HTTPOperationWithRequest:request] responseDescriptors:self.responseDescriptors];
[operation setCompletionBlockWithSuccess:success failure:failure];
- operation.managedObjectContext = managedObjectContext;
+ operation.managedObjectContext = managedObjectContext ?: self.managedObjectStore.mainQueueManagedObjectContext;
operation.managedObjectCache = self.managedObjectStore.managedObjectCache;
operation.fetchRequestBlocks = self.fetchRequestBlocks;
return operation;
@@ -419,7 +447,7 @@ - (id)appropriateObjectRequestOperationWithObject:(id)object
operation = [self objectRequestOperationWithRequest:request success:nil failure:nil];
}
- operation.targetObject = object;
+ if (RKDoesArrayOfResponseDescriptorsContainMappingForClass(self.responseDescriptors, [object class])) operation.targetObject = object;
return operation;
}
@@ -526,6 +554,7 @@ - (RKPaginator *)paginatorWithPathPattern:(NSString *)pathPattern
paginator.managedObjectContext = self.managedObjectStore.mainQueueManagedObjectContext;
paginator.managedObjectCache = self.managedObjectStore.managedObjectCache;
paginator.fetchRequestBlocks = self.fetchRequestBlocks;
+ paginator.operationQueue = self.operationQueue;
return paginator;
}
View
4 Code/Network/RKObjectRequestOperation.h
@@ -73,7 +73,7 @@
@param responseDescriptors An array of `RKResponseDescriptor` objects specifying how object mapping is to be performed on the response loaded by the network operation.
@return The receiver, initialized with the given request and response descriptors.
*/
-- (id)initWithHTTPRequestOperation:(RKHTTPRequestOperation *)requestOperation responseDescriptors:(NSArray *)responseDescriptors;
+- (instancetype)initWithHTTPRequestOperation:(RKHTTPRequestOperation *)requestOperation responseDescriptors:(NSArray *)responseDescriptors;
/**
Initializes an object request operation with a request object and a set of response descriptors.
@@ -87,7 +87,7 @@
@param responseDescriptors An array of `RKResponseDescriptor` objects specifying how object mapping is to be performed on the response loaded by the network operation.
@return The receiver, initialized with the given request and response descriptors.
*/
-- (id)initWithRequest:(NSURLRequest *)request responseDescriptors:(NSArray *)responseDescriptors;
+- (instancetype)initWithRequest:(NSURLRequest *)request responseDescriptors:(NSArray *)responseDescriptors;
///---------------------------------
/// @name Configuring Object Mapping
View
6 Code/Network/RKPaginator.h
@@ -56,9 +56,9 @@
@param responseDescriptors An array of response descriptors describing how to map object representations loaded by object request operations dispatched by the paginator.
@return The receiver, initialized with the request, pagination mapping, and response descriptors.
*/
-- (id)initWithRequest:(NSURLRequest *)request
- paginationMapping:(RKObjectMapping *)paginationMapping
- responseDescriptors:(NSArray *)responseDescriptors;
+- (instancetype)initWithRequest:(NSURLRequest *)request
+ paginationMapping:(RKObjectMapping *)paginationMapping
+ responseDescriptors:(NSArray *)responseDescriptors;
/**
A URL with a path pattern for building a complete URL from
View
6 Code/Network/RKRequestDescriptor.h
@@ -46,9 +46,9 @@
@see [RKObjectMapping requestMapping]
@warning An exception will be raised if the objectClass of the given mapping is not `[NSMutableDictionary class]`.
*/
-+ (id)requestDescriptorWithMapping:(RKMapping *)mapping
- objectClass:(Class)objectClass
- rootKeyPath:(NSString *)rootKeyPath;
++ (instancetype)requestDescriptorWithMapping:(RKMapping *)mapping
+ objectClass:(Class)objectClass
+ rootKeyPath:(NSString *)rootKeyPath;
///-----------------------------------------------------
/// @name Getting Information About a Request Descriptor
View
2 Code/Network/RKRequestDescriptor.m
@@ -52,7 +52,7 @@ @interface RKRequestDescriptor ()
@implementation RKRequestDescriptor
-+ (id)requestDescriptorWithMapping:(RKMapping *)mapping objectClass:(Class)objectClass rootKeyPath:(NSString *)rootKeyPath
++ (instancetype)requestDescriptorWithMapping:(RKMapping *)mapping objectClass:(Class)objectClass rootKeyPath:(NSString *)rootKeyPath
{
NSParameterAssert(mapping);
NSParameterAssert(objectClass);
View
8 Code/Network/RKResponseDescriptor.h
@@ -41,10 +41,10 @@
@param statusCodes A set of HTTP status codes for which the mapping is to be used.
@return A new `RKResponseDescriptor` object.
*/
-+ (RKResponseDescriptor *)responseDescriptorWithMapping:(RKMapping *)mapping
- pathPattern:(NSString *)pathPattern
- keyPath:(NSString *)keyPath
- statusCodes:(NSIndexSet *)statusCodes;
++ (instancetype)responseDescriptorWithMapping:(RKMapping *)mapping
+ pathPattern:(NSString *)pathPattern
+ keyPath:(NSString *)keyPath
+ statusCodes:(NSIndexSet *)statusCodes;
///------------------------------------------------------
/// @name Getting Information About a Response Descriptor
View
8 Code/Network/RKResponseDescriptor.m
@@ -65,10 +65,10 @@ @interface RKResponseDescriptor ()
@implementation RKResponseDescriptor
-+ (RKResponseDescriptor *)responseDescriptorWithMapping:(RKMapping *)mapping
- pathPattern:(NSString *)pathPattern
- keyPath:(NSString *)keyPath
- statusCodes:(NSIndexSet *)statusCodes
++ (instancetype)responseDescriptorWithMapping:(RKMapping *)mapping
+ pathPattern:(NSString *)pathPattern
+ keyPath:(NSString *)keyPath
+ statusCodes:(NSIndexSet *)statusCodes
{
NSParameterAssert(mapping);
RKResponseDescriptor *mappingDescriptor = [self new];
View
10 Code/Network/RKResponseMapperOperation.h
@@ -53,9 +53,9 @@
@param responseDescriptors An array whose elements are `RKResponseDescriptor` objects specifying object mapping configurations that may be applied to the response.
@return The receiver, initialized with the response, data, and response descriptor objects.
*/
-- (id)initWithResponse:(NSHTTPURLResponse *)response
- data:(NSData *)data
- responseDescriptors:(NSArray *)responseDescriptors;
+- (instancetype)initWithResponse:(NSHTTPURLResponse *)response
+ data:(NSData *)data
+ responseDescriptors:(NSArray *)responseDescriptors;
///------------------------------
/// @name Accessing Response Data
@@ -197,9 +197,9 @@
/**
Returns a representation of a mapping result as an `NSError` value.
- The returned `NSError` object is in the `RKErrorDomain` domain and has the `RKMappingErrorFromMappingResult` code. The value for the `NSLocalizedDescriptionKey` is computed by retrieving the objects in the mapping result as an array, evaluating `valueForKeyPath:@"errorMessage"` against the array, and joining the returned error messages by comma to form a single string value. The source error objects are returned with the `NSError` in the `userInfo` dictionary under the `RKObjectMapperErrorObjectsKey` key.
+ The returned `NSError` object is in the `RKErrorDomain` domain and has the `RKMappingErrorFromMappingResult` code. The value for the `NSLocalizedDescriptionKey` is computed by retrieving the objects in the mapping result as an array, evaluating `valueForKeyPath:@"description"` against the array, and joining the returned error messages by comma to form a single string value. The source error objects are returned with the `NSError` in the `userInfo` dictionary under the `RKObjectMapperErrorObjectsKey` key.
- The `errorMessage` property is significant as it is an informal protocol that must be adopted by objects wishing to representing response errors.
+ This implementation assumes that the class used to represent the response error will return a string description of the client side error when sent the `description` message.
@return An error object representing the objects contained in the mapping result.
@see `RKErrorMessage`
View
6 Code/Network/RKResponseMapperOperation.m
@@ -37,7 +37,7 @@
NSArray *collection = [mappingResult array];
NSString *description = nil;
if ([collection count] > 0) {
- description = [[collection valueForKeyPath:@"errorMessage"] componentsJoinedByString:@", "];
+ description = [[collection valueForKeyPath:@"description"] componentsJoinedByString:@", "];
} else {
RKLogWarning(@"Expected mapping result to contain at least one object to construct an error");
}
@@ -287,7 +287,7 @@ @implementation RKObjectResponseMapperOperation
- (RKMappingResult *)performMappingWithObject:(id)sourceObject error:(NSError **)error
{
RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
- self.mapperOperation = [[RKMapperOperation alloc] initWithObject:sourceObject mappingsDictionary:self.responseMappingsDictionary];
+ self.mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:sourceObject mappingsDictionary:self.responseMappingsDictionary];
self.mapperOperation.mappingOperationDataSource = dataSource;
if (NSLocationInRange(self.response.statusCode, RKStatusCodeRangeForClass(RKStatusCodeClassSuccessful))) {
self.mapperOperation.targetObject = self.targetObject;
@@ -327,7 +327,7 @@ - (RKMappingResult *)performMappingWithObject:(id)sourceObject error:(NSError **
self.operationQueue = [NSOperationQueue new];
[self.managedObjectContext performBlockAndWait:^{
// Configure the mapper
- self.mapperOperation = [[RKMapperOperation alloc] initWithObject:sourceObject mappingsDictionary:self.responseMappingsDictionary];
+ self.mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:sourceObject mappingsDictionary:self.responseMappingsDictionary];
self.mapperOperation.delegate = self.mapperDelegate;
// Configure a data source to defer execution of connection operations until mapping is complete
View
6 Code/Network/RKRoute.h
@@ -48,7 +48,7 @@
@param method The request method of the route.
@return A new named route object with the given name, path pattern and request method.
*/
-+ (id)routeWithName:(NSString *)name pathPattern:(NSString *)pathPattern method:(RKRequestMethod)method;
++ (instancetype)routeWithName:(NSString *)name pathPattern:(NSString *)pathPattern method:(RKRequestMethod)method;
/**
Creates and returns a new class route object with the given object class, path pattern and method.
@@ -58,7 +58,7 @@
@param method The request method of the route.
@return A new class route object with the given object class, path pattern and request method.
*/
-+ (id)routeWithClass:(Class)objectClass pathPattern:(NSString *)pathPattern method:(RKRequestMethod)method;
++ (instancetype)routeWithClass:(Class)objectClass pathPattern:(NSString *)pathPattern method:(RKRequestMethod)method;
/**
Creates and returns a new relationship route object with the given relationship name, object class, path pattern and method.
@@ -69,7 +69,7 @@
@param method The request method of the route.
@return A new class route object with the given object class, path pattern and request method.
*/
-+ (id)routeWithRelationshipName:(NSString *)name objectClass:(Class)objectClass pathPattern:(NSString *)pathPattern method:(RKRequestMethod)method;
++ (instancetype)routeWithRelationshipName:(NSString *)name objectClass:(Class)objectClass pathPattern:(NSString *)pathPattern method:(RKRequestMethod)method;
///---------------------------------
/// @name Accessing Route Attributes
View
6 Code/Network/RKRoute.m
@@ -38,7 +38,7 @@ @interface RKRelationshipRoute : RKRoute
@implementation RKRoute
-+ (id)routeWithName:(NSString *)name pathPattern:(NSString *)pathPattern method:(RKRequestMethod)method
++ (instancetype)routeWithName:(NSString *)name pathPattern:(NSString *)pathPattern method:(RKRequestMethod)method
{
NSParameterAssert(name);
NSParameterAssert(pathPattern);
@@ -49,7 +49,7 @@ + (id)routeWithName:(NSString *)name pathPattern:(NSString *)pathPattern method:
return route;
}
-+ (id)routeWithClass:(Class)objectClass pathPattern:(NSString *)pathPattern method:(RKRequestMethod)method
++ (instancetype)routeWithClass:(Class)objectClass pathPattern:(NSString *)pathPattern method:(RKRequestMethod)method
{
NSParameterAssert(objectClass);
NSParameterAssert(pathPattern);
@@ -60,7 +60,7 @@ + (id)routeWithClass:(Class)objectClass pathPattern:(NSString *)pathPattern meth
return route;
}
-+ (id)routeWithRelationshipName:(NSString *)relationshipName objectClass:(Class)objectClass pathPattern:(NSString *)pathPattern method:(RKRequestMethod)method
++ (instancetype)routeWithRelationshipName:(NSString *)relationshipName objectClass:(Class)objectClass pathPattern:(NSString *)pathPattern method:(RKRequestMethod)method
{
NSParameterAssert(relationshipName);
NSParameterAssert(objectClass);
View
2 Code/Network/RKRouter.h
@@ -49,7 +49,7 @@
@param baseURL The base URL with which to initialize the receiver.
@return The receiver, initialized with the given base URL.
*/
-- (id)initWithBaseURL:(NSURL *)baseURL;
+- (instancetype)initWithBaseURL:(NSURL *)baseURL;
///----------------------
/// @name Generating URLs
View
4 Code/ObjectMapping/RKAttributeMapping.h
@@ -51,10 +51,10 @@
mapped and attempts to transform the source content into the type of the desination property specified by the mapping. In this case,
an NSDateFormatter object would be used to process the inbound `NSString` into an outbound `NSDate` object.
- @param sourceKeyPath The key path on the source object from which to read the data being mapped.
+ @param sourceKeyPath The key path on the source object from which to read the data being mapped. If `nil`, then the entire source object representation is mapped to the specified destination attribute.
@param destinationKeyPath The key path on the destination object on which to set the mapped data.
@return A newly created attribute mapping object that is ready to be added to an object mapping.
*/
-+ (RKAttributeMapping *)attributeMappingFromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath;
++ (instancetype)attributeMappingFromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath;
@end
View
3 Code/ObjectMapping/RKAttributeMapping.m
@@ -27,9 +27,8 @@ @interface RKAttributeMapping ()
@implementation RKAttributeMapping
-+ (RKAttributeMapping *)attributeMappingFromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath
++ (instancetype)attributeMappingFromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath
{
- NSParameterAssert(sourceKeyPath);
NSParameterAssert(destinationKeyPath);
RKAttributeMapping *attributeMapping = [self new];
attributeMapping.sourceKeyPath = sourceKeyPath;
View
4 Code/ObjectMapping/RKErrorMessage.h
@@ -23,10 +23,6 @@
/**
The `RKErrorMessage` is a simple class used for representing error messages returned by a remote backend system with which the client application is communicating. Error messages are typically returned in a response body in the Client Error class (status code 4xx range).
- ## Error Message Informal Protocol
-
- The `errorMessage` property method is the sole method of an informal protocol that must be adopted by objects wishing to represent error messages within RestKit. This protocol is by the `RKErrorFromMappingResult` function when constructing `NSError` messages from a mapped response body.
-
@see `RKErrorFromMappingResult`
*/
@interface RKErrorMessage : NSObject
View
3 Code/ObjectMapping/RKErrorMessage.m
@@ -24,8 +24,7 @@ @implementation RKErrorMessage
- (NSString *)description
{
- return [NSString stringWithFormat:@"<%@:%p error message = \"%@\" userInfo = %@>",
- NSStringFromClass([self class]), self, self.errorMessage, self.userInfo];
+ return self.errorMessage;
}
@end
View
32 Code/ObjectMapping/RKMapperOperation.h
@@ -37,7 +37,7 @@
## Mappings Dictionary
- 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:
+ 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:
{ "tags": [ "hacking", "phreaking" ], "authors": [ "Captain Crunch", "Emmanuel Goldstein" ], "magazine": { "title": "2600 The Hacker Quarterly" } }
@@ -50,17 +50,19 @@
Note that the keys of the dictionary are **key paths**. Deeply nested content can be mapped by specifying the full key path as the key of the mappings dictionary.
- ### The NSNull Key
+ ### Mapping the Root Object Representation
- 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.
+ 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.
+
+ Note that it is possible to map the same representation with multiple mappings, including a combination of a root key mapping and nested keypaths.
## Data Source
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.
## Target Object
- 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.
+ 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.
## Core Data
@@ -75,11 +77,11 @@
/**
Initializes the operation with a source object and a mappings dictionary.
- @param object An `NSDictionary` or `NSArray` of `NSDictionary` object representations to be mapped into local domain objects.
+ @param representation An `NSDictionary` or `NSArray` of `NSDictionary` object representations to be mapped into local domain objects.
@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.
@return The receiver, initialized with the given object and and dictionary of key paths to mappings.
*/
-- (id)initWithObject:(id)object mappingsDictionary:(NSDictionary *)mappingsDictionary;
+- (instancetype)initWithRepresentation:(id)representation mappingsDictionary:(NSDictionary *)mappingsDictionary;
///------------------------------------------
/// @name Accessing Mapping Result and Errors
@@ -100,14 +102,14 @@
///-------------------------------------
/**
- The source object representation against which the mapping is performed.
+ The representation of one or more objects against which the mapping is performed.
Either an `NSDictionary` or an `NSArray` of `NSDictionary` objects.
*/
-@property (nonatomic, strong, readonly) id sourceObject;
+@property (nonatomic, strong, readonly) id representation;
/**
- A dictionary of key paths to `RKMapping` objects specifying how object representations in the `sourceObject` are to be mapped.
+ A dictionary of key paths to `RKMapping` objects specifying how object representations in the `representation` are to be mapped.
Please see the above discussion for in-depth details about the mappings dictionary.
*/
@@ -130,6 +132,8 @@
*/
@property (nonatomic, weak) id<RKMapperOperationDelegate> delegate;
+- (BOOL)execute:(NSError **)error;
+
@end
///--------------------------------------
@@ -177,7 +181,7 @@
@param mapper The mapper operation performing the mapping.
@param dictionaryOrArrayOfDictionaries The `NSDictictionary` or `NSArray` of `NSDictionary` object representations that was found at the `keyPath`.
- @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.
+ @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.
*/
- (void)mapper:(RKMapperOperation *)mapper didFindRepresentationOrArrayOfRepresentations:(id)dictionaryOrArrayOfDictionaries atKeyPath:(NSString *)keyPath;
@@ -194,11 +198,11 @@
///----------------------------------------------
/**
- Tells the delegate that the mapper is about to start a mapping operation to map a representation found in the `sourceObject`.
+ Tells the delegate that the mapper is about to start a mapping operation to map a representation found in the `representation`.
@param mapper The mapper operation performing the mapping.
@param mappingOperation The mapping operation that is about to be started.
- @param keyPath The key path that was mapped. A `nil` key path indicates that the mapping matched the entire `sourceObject`.
+ @param keyPath The key path that was mapped. A `nil` key path indicates that the mapping matched the entire `representation`.
*/
- (void)mapper:(RKMapperOperation *)mapper willStartMappingOperation:(RKMappingOperation *)mappingOperation forKeyPath:(NSString *)keyPath;
@@ -207,7 +211,7 @@
@param mapper The mapper operation performing the mapping.
@param mappingOperation The mapping operation that has finished.
- @param keyPath The key path that was mapped. A `nil` key path indicates that the mapping matched the entire `sourceObject`.
+ @param keyPath The key path that was mapped. A `nil` key path indicates that the mapping matched the entire `representation`.
*/
- (void)mapper:(RKMapperOperation *)mapper didFinishMappingOperation:(RKMappingOperation *)mappingOperation forKeyPath:(NSString *)keyPath;
@@ -216,7 +220,7 @@
@param mapper The mapper operation performing the mapping.
@param mappingOperation The mapping operation that has failed.
- @param keyPath The key path that was mapped. A `nil` key path indicates that the mapping matched the entire `sourceObject`.
+ @param keyPath The key path that was mapped. A `nil` key path indicates that the mapping matched the entire `representation`.
@param error The error that occurred during the execution of the mapping operation.
*/
- (void)mapper:(RKMapperOperation *)mapper didFailMappingOperation:(RKMappingOperation *)mappingOperation forKeyPath:(NSString *)keyPath withError:(NSError *)error;
View
110 Code/ObjectMapping/RKMapperOperation.m
@@ -37,23 +37,35 @@
return ([keyPath isEqual:[NSNull null]]) ? nil : keyPath;
}
+
+static NSString *RKFailureReasonErrorStringForMappingNotFoundError(id representation, NSDictionary *mappingsDictionary)
+{
+ NSMutableString *failureReason = [NSMutableString string];
+ [failureReason appendFormat:@"The mapping operation was unable to find any nested object representations at the key paths searched: %@", [[mappingsDictionary allKeys] componentsJoinedByString:@", "]];
+ if ([representation respondsToSelector:@selector(allKeys)]) {
+ [failureReason appendFormat:@"\nThe representation inputted to the mapper was found to contain nested object representations at the following key paths: %@", [[representation allKeys] componentsJoinedByString:@", "]];
+ }
+ [failureReason appendFormat:@"\nThis likely indicates that you have misconfigured the key paths for your mappings."];
+ return failureReason;
+}
+
@interface RKMapperOperation ()
@property (nonatomic, strong, readwrite) NSError *error;
@property (nonatomic, strong, readwrite) RKMappingResult *mappingResult;
@property (nonatomic, strong) NSMutableArray *mappingErrors;
-@property (nonatomic, strong) id sourceObject;
+@property (nonatomic, strong) id representation;
@property (nonatomic, strong, readwrite) NSDictionary *mappingsDictionary;
@end
@implementation RKMapperOperation
-- (id)initWithObject:(id)object mappingsDictionary:(NSDictionary *)mappingsDictionary;
+- (id)initWithRepresentation:(id)representation mappingsDictionary:(NSDictionary *)mappingsDictionary;
{
self = [super init];
if (self) {
- self.sourceObject = object;
+ self.representation = representation;
self.mappingsDictionary = mappingsDictionary;
self.mappingErrors = [NSMutableArray new];
self.mappingOperationDataSource = [RKObjectMappingOperationDataSource new];
@@ -117,16 +129,17 @@ - (BOOL)isNullCollection:(id)object
#pragma mark - Mapping Primitives
-- (id)mapObject:(id)mappableObject atKeyPath:(NSString *)keyPath usingMapping:(RKMapping *)mapping
+// Maps a singular object representation
+- (id)mapRepresentation:(id)representation atKeyPath:(NSString *)keyPath usingMapping:(RKMapping *)mapping
{
- NSAssert([mappableObject respondsToSelector:@selector(setValue:forKeyPath:)], @"Expected self.object to be KVC compliant");
+ NSAssert([representation respondsToSelector:@selector(setValue:forKeyPath:)], @"Expected self.object to be KVC compliant");
id destinationObject = nil;
if (self.targetObject) {
destinationObject = self.targetObject;
RKObjectMapping *objectMapping = nil;
if ([mapping isKindOfClass:[RKDynamicMapping class]]) {
- objectMapping = [(RKDynamicMapping *)mapping objectMappingForRepresentation:mappableObject];
+ objectMapping = [(RKDynamicMapping *)mapping objectMappingForRepresentation:representation];
} else if ([mapping isKindOfClass:[RKObjectMapping class]]) {
objectMapping = (RKObjectMapping *)mapping;
} else {
@@ -142,15 +155,15 @@ - (id)mapObject:(id)mappableObject atKeyPath:(NSString *)keyPath usingMapping:(R
return nil;
} else {
// There is more than one mapping present. We are likely mapping secondary key paths to new objects
- destinationObject = [self objectWithMapping:mapping andData:mappableObject];
+ destinationObject = [self objectForRepresentation:representation withMapping:mapping];
}
}
} else {
- destinationObject = [self objectWithMapping:mapping andData:mappableObject];
+ destinationObject = [self objectForRepresentation:representation withMapping:mapping];
}
if (mapping && destinationObject) {
- BOOL success = [self mapFromObject:mappableObject toObject:destinationObject atKeyPath:keyPath usingMapping:mapping];
+ BOOL success = [self mapRepresentation:representation toObject:destinationObject atKeyPath:keyPath usingMapping:mapping];
if (success) {
return destinationObject;
}
@@ -163,28 +176,29 @@ - (id)mapObject:(id)mappableObject atKeyPath:(NSString *)keyPath usingMapping:(R
return nil;
}
-- (NSArray *)mapCollection:(NSArray *)mappableObjects atKeyPath:(NSString *)keyPath usingMapping:(RKMapping *)mapping
+// Map a collection of object representations
+- (NSArray *)mapRepresentations:(id)representations atKeyPath:(NSString *)keyPath usingMapping:(RKMapping *)mapping
{
- NSAssert(mappableObjects != nil, @"Cannot map without an collection of mappable objects");
+ NSAssert(representations != nil, @"Cannot map without an collection of mappable objects");
NSAssert(mapping != nil, @"Cannot map without a mapping to consult");
- NSArray *objectsToMap = mappableObjects;
+ NSArray *objectsToMap = representations;
if (mapping.forceCollectionMapping) {
// If we have forced mapping of a dictionary, map each subdictionary
- if ([mappableObjects isKindOfClass:[NSDictionary class]]) {
+ if ([representations isKindOfClass:[NSDictionary class]]) {
RKLogDebug(@"Collection mapping forced for NSDictionary, mapping each key/value independently...");
- objectsToMap = [NSMutableArray arrayWithCapacity:[mappableObjects count]];
- for (id key in mappableObjects) {
- NSDictionary *dictionaryToMap = [NSDictionary dictionaryWithObject:[mappableObjects valueForKey:key] forKey:key];
+ objectsToMap = [NSMutableArray arrayWithCapacity:[representations count]];
+ for (id key in representations) {
+ NSDictionary *dictionaryToMap = [NSDictionary dictionaryWithObject:[representations valueForKey:key] forKey:key];
[(NSMutableArray *)objectsToMap addObject:dictionaryToMap];
}
} else {
- RKLogWarning(@"Collection mapping forced but mappable objects is of type '%@' rather than NSDictionary", NSStringFromClass([mappableObjects class]));
+ RKLogWarning(@"Collection mapping forced but representations is of type '%@' rather than NSDictionary", NSStringFromClass([representations class]));
}
}
// Ensure we are mapping onto a mutable collection if there is a target
- NSMutableArray *mappedObjects = self.targetObject ? self.targetObject : [NSMutableArray arrayWithCapacity:[mappableObjects count]];
+ NSMutableArray *mappedObjects = self.targetObject ? self.targetObject : [NSMutableArray arrayWithCapacity:[representations count]];
if (NO == [mappedObjects respondsToSelector:@selector(addObject:)]) {
NSString *errorMessage = [NSString stringWithFormat:
@"Cannot map a collection of objects onto a non-mutable collection. Unexpected destination object type '%@'",
@@ -194,12 +208,12 @@ - (NSArray *)mapCollection:(NSArray *)mappableObjects atKeyPath:(NSString *)keyP
}
for (id mappableObject in objectsToMap) {
- id destinationObject = [self objectWithMapping:mapping andData:mappableObject];
+ id destinationObject = [self objectForRepresentation:mappableObject withMapping:mapping];
if (! destinationObject) {
continue;
}
- BOOL success = [self mapFromObject:mappableObject toObject:destinationObject atKeyPath:keyPath usingMapping:mapping];
+ BOOL success = [self mapRepresentation:mappableObject toObject:destinationObject atKeyPath:keyPath usingMapping:mapping];
if (success) {
[mappedObjects addObject:destinationObject];
}
@@ -209,7 +223,7 @@ - (NSArray *)mapCollection:(NSArray *)mappableObjects atKeyPath:(NSString *)keyP
}
// The workhorse of this entire process. Emits object loading operations
-- (BOOL)mapFromObject:(id)mappableObject toObject:(id)destinationObject atKeyPath:(NSString *)keyPath usingMapping:(RKMapping *)mapping
+- (BOOL)mapRepresentation:(id)mappableObject toObject:(id)destinationObject atKeyPath:(NSString *)keyPath usingMapping:(RKMapping *)mapping
{
NSAssert(destinationObject != nil, @"Cannot map without a target object to assign the results to");
NSAssert(mappableObject != nil, @"Cannot map without a collection of attributes");
@@ -239,16 +253,16 @@ - (BOOL)mapFromObject:(id)mappableObject toObject:(id)destinationObject atKeyPat
}
}
-- (id)objectWithMapping:(RKMapping *)mapping andData:(id)mappableData
+- (id)objectForRepresentation:(id)representation withMapping:(RKMapping *)mapping
{
NSAssert([mapping isKindOfClass:[RKMapping class]], @"Expected an RKMapping object");
NSAssert(self.mappingOperationDataSource, @"Cannot find or instantiate objects without a data source");
RKObjectMapping *objectMapping = nil;
if ([mapping isKindOfClass:[RKDynamicMapping class]]) {
- objectMapping = [(RKDynamicMapping *)mapping objectMappingForRepresentation:mappableData];
+ objectMapping = [(RKDynamicMapping *)mapping objectMappingForRepresentation:representation];
if (! objectMapping) {
- RKLogDebug(@"Mapping %@ declined mapping for data %@: returned nil objectMapping", mapping, mappableData);
+ RKLogDebug(@"Mapping %@ declined mapping for representation %@: returned nil objectMapping", mapping, representation);
}
} else if ([mapping isKindOfClass:[RKObjectMapping class]]) {
objectMapping = (RKObjectMapping *)mapping;
@@ -257,46 +271,48 @@ - (id)objectWithMapping:(RKMapping *)mapping andData:(id)mappableData
}
if (objectMapping) {
- return [self.mappingOperationDataSource mappingOperation:nil targetObjectForRepresentation:mappableData withMapping:objectMapping];