diff --git a/Code/CoreData/NSManagedObjectContext+RKAdditions.m b/Code/CoreData/NSManagedObjectContext+RKAdditions.m index d52546f251..8f22e10c19 100644 --- a/Code/CoreData/NSManagedObjectContext+RKAdditions.m +++ b/Code/CoreData/NSManagedObjectContext+RKAdditions.m @@ -52,8 +52,11 @@ - (BOOL)saveToPersistentStore:(NSError **)error 4. Save the child context to the parent context (the main one) which will work, 5. Save the main context - a NSObjectInaccessibleException will occur and Core Data will either crash your app or lock it up (a semaphore is not correctly released on the first error so the next fetch request will block forever. */ - [contextToSave obtainPermanentIDsForObjects:[[contextToSave insertedObjects] allObjects] error:&localError]; - if (localError) { + __block BOOL obtained; + [contextToSave performBlockAndWait:^{ + obtained = [contextToSave obtainPermanentIDsForObjects:[[contextToSave insertedObjects] allObjects] error:&localError]; + }]; + if (!obtained) { if (error) *error = localError; return NO; } diff --git a/Code/CoreData/RKManagedObjectMappingOperationDataSource.m b/Code/CoreData/RKManagedObjectMappingOperationDataSource.m index 245abe31c9..0827616793 100644 --- a/Code/CoreData/RKManagedObjectMappingOperationDataSource.m +++ b/Code/CoreData/RKManagedObjectMappingOperationDataSource.m @@ -71,7 +71,7 @@ static id RKValueForAttributeMappingInRepresentation(RKAttributeMapping *attribu __block NSError *error = nil; // If the representation is mapped with a nesting attribute, we must apply the nesting value to the representation before constructing the identification attributes - RKAttributeMapping *nestingAttributeMapping = [[entityMapping propertyMappingsBySourceKeyPath] objectForKey:RKObjectMappingNestingAttributeKeyName]; + RKAttributeMapping *nestingAttributeMapping = [entityMapping mappingForSourceKeyPath:RKObjectMappingNestingAttributeKeyName]; if (nestingAttributeMapping) { Class attributeClass = [entityMapping classForProperty:nestingAttributeMapping.destinationKeyPath]; id attributeValue = nil; @@ -216,6 +216,15 @@ - (void)dealloc [[NSNotificationCenter defaultCenter] removeObserver:self]; } +- (id)mappingOperation:(RKMappingOperation *)mappingOperation targetObjectForMapping:(RKObjectMapping *)mapping inRelationship:(RKRelationshipMapping *)relationship +{ + if (! [mapping isKindOfClass:[RKEntityMapping class]]) { + return [mapping.objectClass new]; + } + + return nil; +} + - (id)mappingOperation:(RKMappingOperation *)mappingOperation targetObjectForRepresentation:(NSDictionary *)representation withMapping:(RKObjectMapping *)mapping inRelationship:(RKRelationshipMapping *)relationship { NSAssert(representation, @"Mappable data cannot be nil"); @@ -440,10 +449,11 @@ - (BOOL)mappingOperation:(RKMappingOperation *)mappingOperation deleteExistingVa - (BOOL)mappingOperationShouldSetUnchangedValues:(RKMappingOperation *)mappingOperation { // Only new objects should have a temporary ID - if ([mappingOperation.destinationObject isKindOfClass:[NSManagedObject class]] && [[(NSManagedObject *)mappingOperation.destinationObject objectID] isTemporaryID]) { - return YES; + if ([mappingOperation.destinationObject isKindOfClass:[NSManagedObject class]]) { + return [[(NSManagedObject *)mappingOperation.destinationObject objectID] isTemporaryID]; } - else return NO; + + return [mappingOperation isNewDestinationObject]; } - (BOOL)mappingOperationShouldSkipPropertyMapping:(RKMappingOperation *)mappingOperation @@ -456,7 +466,7 @@ - (BOOL)mappingOperationShouldSkipPropertyMapping:(RKMappingOperation *)mappingO if (! currentValue) return NO; if (! [currentValue respondsToSelector:@selector(compare:)]) return NO; - RKPropertyMapping *propertyMappingForModificationKey = [[(RKEntityMapping *)mappingOperation.mapping propertyMappingsByDestinationKeyPath] objectForKey:modificationKey]; + RKPropertyMapping *propertyMappingForModificationKey = [(RKEntityMapping *)mappingOperation.mapping mappingForDestinationKeyPath:modificationKey]; id rawValue = [[mappingOperation sourceObject] valueForKeyPath:propertyMappingForModificationKey.sourceKeyPath]; if (! rawValue) return NO; Class attributeClass = [entityMapping classForProperty:propertyMappingForModificationKey.destinationKeyPath]; @@ -474,4 +484,9 @@ - (BOOL)mappingOperationShouldSkipPropertyMapping:(RKMappingOperation *)mappingO } } +- (BOOL)mappingOperationShouldCollectMappingInfo:(RKMappingOperation *)mappingOperation +{ + return YES; +} + @end diff --git a/Code/CoreData/RKManagedObjectStore.m b/Code/CoreData/RKManagedObjectStore.m index 65586f0715..8d9571ce50 100644 --- a/Code/CoreData/RKManagedObjectStore.m +++ b/Code/CoreData/RKManagedObjectStore.m @@ -335,8 +335,12 @@ - (void)recreateManagedObjectContexts - (BOOL)resetPersistentStores:(NSError **)error { - [self.mainQueueManagedObjectContext reset]; - [self.persistentStoreManagedObjectContext reset]; + [self.mainQueueManagedObjectContext performBlockAndWait:^{ + [self.mainQueueManagedObjectContext reset]; + }]; + [self.persistentStoreManagedObjectContext performBlockAndWait:^{ + [self.persistentStoreManagedObjectContext reset]; + }]; NSError *localError; for (NSPersistentStore *persistentStore in self.persistentStoreCoordinator.persistentStores) { diff --git a/Code/CoreData/RKPropertyInspector+CoreData.m b/Code/CoreData/RKPropertyInspector+CoreData.m index 92fb490e49..7aaaa3351c 100644 --- a/Code/CoreData/RKPropertyInspector+CoreData.m +++ b/Code/CoreData/RKPropertyInspector+CoreData.m @@ -52,10 +52,11 @@ - (NSDictionary *)propertyInspectionForEntity:(NSEntityDescription *)entity if ([cls isSubclassOfClass:[NSNumber class]] && [attributeDescription attributeType] == NSBooleanAttributeType) { cls = objc_getClass("NSCFBoolean") ?: objc_getClass("__NSCFBoolean") ?: cls; } - NSDictionary *propertyInspection = @{ RKPropertyInspectionNameKey: name, - RKPropertyInspectionKeyValueCodingClassKey: cls, - RKPropertyInspectionIsPrimitiveKey: @(NO) }; - [entityInspection setValue:propertyInspection forKey:name]; + RKPropertyInspectorPropertyInfo *info; + info = [RKPropertyInspectorPropertyInfo propertyInfoWithName:name + keyValueClass:cls + isPrimitive:NO]; + [entityInspection setValue:info forKey:name]; } else if ([attributeDescription attributeType] == NSTransformableAttributeType && ![name isEqualToString:@"_mapkit_hasPanoramaID"]) { @@ -71,10 +72,11 @@ - (NSDictionary *)propertyInspectionForEntity:(NSEntityDescription *)entity const char *attr = property_getAttributes(prop); Class destinationClass = RKKeyValueCodingClassFromPropertyAttributes(attr); if (destinationClass) { - NSDictionary *propertyInspection = @{ RKPropertyInspectionNameKey: name, - RKPropertyInspectionKeyValueCodingClassKey: destinationClass, - RKPropertyInspectionIsPrimitiveKey: @(NO) }; - [entityInspection setObject:propertyInspection forKey:name]; + RKPropertyInspectorPropertyInfo *info; + info = [RKPropertyInspectorPropertyInfo propertyInfoWithName:name + keyValueClass:destinationClass + isPrimitive:NO]; + [entityInspection setObject:info forKey:name]; } } } @@ -84,15 +86,17 @@ - (NSDictionary *)propertyInspectionForEntity:(NSEntityDescription *)entity NSRelationshipDescription *relationshipDescription = [[entity relationshipsByName] valueForKey:name]; if ([relationshipDescription isToMany]) { if ([relationshipDescription isOrdered]) { - NSDictionary *propertyInspection = @{ RKPropertyInspectionNameKey: name, - RKPropertyInspectionKeyValueCodingClassKey: [NSOrderedSet class], - RKPropertyInspectionIsPrimitiveKey: @(NO) }; - [entityInspection setObject:propertyInspection forKey:name]; + RKPropertyInspectorPropertyInfo *info; + info = [RKPropertyInspectorPropertyInfo propertyInfoWithName:name + keyValueClass:[NSOrderedSet class] + isPrimitive:NO]; + [entityInspection setObject:info forKey:name]; } else { - NSDictionary *propertyInspection = @{ RKPropertyInspectionNameKey: name, - RKPropertyInspectionKeyValueCodingClassKey: [NSSet class], - RKPropertyInspectionIsPrimitiveKey: @(NO) }; - [entityInspection setObject:propertyInspection forKey:name]; + RKPropertyInspectorPropertyInfo *info; + info = [RKPropertyInspectorPropertyInfo propertyInfoWithName:name + keyValueClass:[NSSet class] + isPrimitive:NO]; + [entityInspection setObject:info forKey:name]; } } else { NSEntityDescription *destinationEntity = [relationshipDescription destinationEntity]; @@ -100,10 +104,11 @@ - (NSDictionary *)propertyInspectionForEntity:(NSEntityDescription *)entity if (! destinationClass) { RKLogWarning(@"Retrieved `Nil` value for class named '%@': This likely indicates that the class is invalid or does not exist in the current target.", [destinationEntity managedObjectClassName]); } - NSDictionary *propertyInspection = @{ RKPropertyInspectionNameKey: name, - RKPropertyInspectionKeyValueCodingClassKey: destinationClass ?: [NSNull null], - RKPropertyInspectionIsPrimitiveKey: @(NO) }; - [entityInspection setObject:propertyInspection forKey:name]; + RKPropertyInspectorPropertyInfo *info; + info = [RKPropertyInspectorPropertyInfo propertyInfoWithName:name + keyValueClass:destinationClass ?: [NSNull null] + isPrimitive:NO]; + [entityInspection setObject:info forKey:name]; } } @@ -117,8 +122,8 @@ - (NSDictionary *)propertyInspectionForEntity:(NSEntityDescription *)entity - (Class)classForPropertyNamed:(NSString *)propertyName ofEntity:(NSEntityDescription *)entity { NSDictionary *entityInspection = [self propertyInspectionForEntity:entity]; - NSDictionary *propertyInspection = [entityInspection objectForKey:propertyName]; - return [propertyInspection objectForKey:RKPropertyInspectionKeyValueCodingClassKey]; + RKPropertyInspectorPropertyInfo *propertyInspection = [entityInspection objectForKey:propertyName]; + return propertyInspection.keyValueCodingClass; } @end @@ -131,13 +136,21 @@ @implementation NSManagedObject (RKPropertyInspection) - (Class)rk_classForPropertyAtKeyPath:(NSString *)keyPath isPrimitive:(BOOL *)isPrimitive { - NSArray *components = [keyPath componentsSeparatedByString:@"."]; + NSRange dotRange = [keyPath rangeOfString:@"." options:NSLiteralSearch]; + RKPropertyInspector *inspector = [RKPropertyInspector sharedInspector]; Class currentPropertyClass = [self class]; Class propertyClass = nil; + + if (dotRange.length == 0) { + propertyClass = [inspector classForPropertyNamed:keyPath ofEntity:[self entity]]; + return propertyClass ?: [inspector classForPropertyNamed:keyPath ofClass:currentPropertyClass isPrimitive:isPrimitive]; + } + + NSArray *components = [keyPath componentsSeparatedByString:@"."]; 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:currentPropertyClass isPrimitive:isPrimitive]; + propertyClass = [inspector classForPropertyNamed:property ofEntity:[self entity]]; + propertyClass = propertyClass ?: [inspector classForPropertyNamed:property ofClass:currentPropertyClass isPrimitive:isPrimitive]; if (! propertyClass) break; currentPropertyClass = propertyClass; } diff --git a/Code/CoreData/RKRelationshipConnectionOperation.m b/Code/CoreData/RKRelationshipConnectionOperation.m index 18a8e967a9..bf583b78d9 100644 --- a/Code/CoreData/RKRelationshipConnectionOperation.m +++ b/Code/CoreData/RKRelationshipConnectionOperation.m @@ -182,7 +182,11 @@ - (id)findConnectedValueForConnection:(RKConnectionDescription *)connection shou - (void)main { for (RKConnectionDescription *connection in self.connections) { - if (self.isCancelled || [self.managedObject isDeleted]) return; + __block BOOL isDeleted; + [self.managedObject.managedObjectContext performBlockAndWait:^{ + isDeleted=[self.managedObject isDeleted]; + }]; + if (self.isCancelled || isDeleted) return; NSString *relationshipName = connection.relationship.name; RKLogTrace(@"Connecting relationship '%@' with mapping: %@", relationshipName, connection); diff --git a/Code/Network/RKManagedObjectRequestOperation.m b/Code/Network/RKManagedObjectRequestOperation.m index bff12dedd3..b3e46d7d44 100644 --- a/Code/Network/RKManagedObjectRequestOperation.m +++ b/Code/Network/RKManagedObjectRequestOperation.m @@ -824,7 +824,11 @@ - (BOOL)saveContext:(NSError **)error }]; } - if ([self.privateContext hasChanges]) { + __block BOOL hasChanges; + [self.privateContext performBlockAndWait:^{ + hasChanges = [self.privateContext hasChanges]; + }]; + if (hasChanges) { return [self saveContext:self.privateContext error:error]; } else if ([self.targetObject isKindOfClass:[NSManagedObject class]]) { NSManagedObjectContext *context = [(NSManagedObject *)self.targetObject managedObjectContext]; diff --git a/Code/Network/RKObjectRequestOperation.m b/Code/Network/RKObjectRequestOperation.m index 17583582d0..b51450b1a4 100644 --- a/Code/Network/RKObjectRequestOperation.m +++ b/Code/Network/RKObjectRequestOperation.m @@ -171,7 +171,8 @@ - (void)HTTPOperationDidFinish:(NSNotification *)notification 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], statusCodeAndElapsedTime, operation.error, operation.responseString); + RKLogError(@"%@ '%@' %@:\nerror=%@", [operation.request HTTPMethod], [[operation.request URL] absoluteString], statusCodeAndElapsedTime, operation.error); + RKLogDebug(@"response.body=%@", operation.responseString); } else { if (operation.error.code == NSURLErrorCancelled) { RKLogError(@"%@ '%@' %@: Cancelled", [operation.request HTTPMethod], [[operation.request URL] absoluteString], statusCodeAndElapsedTime); @@ -205,7 +206,8 @@ - (void)objectRequestOperationDidFinish:(NSNotification *)notification NSString *statusCodeAndElapsedTime = [NSString stringWithFormat:@"(%ld%@/ %lu objects) %@", (long)[HTTPRequestOperation.response statusCode], statusCodeDescription, (unsigned long) [objectRequestOperation.mappingResult count], elapsedTimeString]; if (objectRequestOperation.error) { if ((_RKlcl_component_level[(__RKlcl_log_symbol(RKlcl_cRestKitNetwork))]) >= (__RKlcl_log_symbol(RKlcl_vTrace))) { - RKLogError(@"%@ '%@' %@:\nerror=%@\nresponse.body=%@", [HTTPRequestOperation.request HTTPMethod], [[HTTPRequestOperation.request URL] absoluteString], statusCodeAndElapsedTime, objectRequestOperation.error, HTTPRequestOperation.responseString); + RKLogError(@"%@ '%@' %@:\nerror=%@", [HTTPRequestOperation.request HTTPMethod], [[HTTPRequestOperation.request URL] absoluteString], statusCodeAndElapsedTime, objectRequestOperation.error); + RKLogDebug(@"response.body=%@", HTTPRequestOperation.responseString); } else { if (objectRequestOperation.error.code == NSURLErrorCancelled) { RKLogError(@"%@ '%@' %@: Cancelled", [HTTPRequestOperation.request HTTPMethod], [[HTTPRequestOperation.request URL] absoluteString], statusCodeAndElapsedTime); diff --git a/Code/ObjectMapping/RKDynamicMapping.m b/Code/ObjectMapping/RKDynamicMapping.m index 3b9a8b1664..8b647146ed 100644 --- a/Code/ObjectMapping/RKDynamicMapping.m +++ b/Code/ObjectMapping/RKDynamicMapping.m @@ -28,6 +28,7 @@ @interface RKDynamicMapping () @property (nonatomic, strong) NSMutableArray *mutableMatchers; +@property (nonatomic, strong) NSArray *possibleObjectMappings; @property (nonatomic, copy) RKObjectMapping *(^objectMappingForRepresentationBlock)(id representation); @end @@ -38,6 +39,7 @@ - (id)init self = [super init]; if (self) { self.mutableMatchers = [NSMutableArray new]; + self.possibleObjectMappings = [NSArray new]; } return self; @@ -50,7 +52,7 @@ - (NSArray *)matchers - (NSArray *)objectMappings { - return [self.mutableMatchers valueForKey:@"objectMapping"]; + return self.possibleObjectMappings; } - (void)addMatcher:(RKObjectMappingMatcher *)matcher @@ -61,13 +63,29 @@ - (void)addMatcher:(RKObjectMappingMatcher *)matcher [self.mutableMatchers insertObject:matcher atIndex:0]; } else { [self.mutableMatchers addObject:matcher]; + + NSArray *newPossibleMappings = [matcher possibleObjectMappings]; + if (newPossibleMappings.count > 0) { + self.possibleObjectMappings = [self.possibleObjectMappings arrayByAddingObjectsFromArray:newPossibleMappings]; + } } } - (void)removeMatcher:(RKObjectMappingMatcher *)matcher { NSParameterAssert(matcher); - [self.mutableMatchers removeObject:matcher]; + + if ([self.mutableMatchers containsObject:matcher]) { + NSMutableArray *mappings = [self.possibleObjectMappings mutableCopy]; + for (RKObjectMapping *mapping in [matcher possibleObjectMappings]) { + /* removeObject will remove *all* instances; if we have dups we just want to remove one */ + NSUInteger idx = [mappings indexOfObject:mapping]; + if (idx != NSNotFound) + [mappings removeObjectAtIndex:idx]; + } + self.possibleObjectMappings = [mappings copy]; + [self.mutableMatchers removeObject:matcher]; + } } - (RKObjectMapping *)objectMappingForRepresentation:(id)representation diff --git a/Code/ObjectMapping/RKMapperOperation.m b/Code/ObjectMapping/RKMapperOperation.m index c13a4bca8b..009732cc0c 100644 --- a/Code/ObjectMapping/RKMapperOperation.m +++ b/Code/ObjectMapping/RKMapperOperation.m @@ -51,8 +51,21 @@ } // Duplicating interface from `RKMappingOperation.m` -@interface RKMappingSourceObject : NSProxy -- (id)initWithObject:(id)object parentObject:(id)parentObject rootObject:(id)rootObject metadata:(NSDictionary *)metadata; +@interface RKMappingSourceObject : NSObject +- (id)initWithObject:(id)object parentObject:(id)parentObject rootObject:(id)rootObject metadata:(NSArray *)metadata; +@end + +@interface RKMappingOperation (Private) +@property (nonatomic, readwrite, getter=isNewDestinationObject) BOOL newDestinationObject; +@end + +@interface RKMapperMetadata : NSObject +@property NSUInteger collectionIndex; +@property NSString *rootKeyPath; +@end + +@implementation RKMapperMetadata +- (id)valueForUndefinedKey:(NSString *)key { return nil; } @end @interface RKMapperOperation () @@ -143,7 +156,8 @@ - (id)mapRepresentation:(id)representation atKeyPath:(NSString *)keyPath usingMa { NSAssert([representation respondsToSelector:@selector(setValue:forKeyPath:)], @"Expected self.object to be KVC compliant"); id destinationObject = nil; - + BOOL isNewObject = NO; + if (self.targetObject) { destinationObject = self.targetObject; RKObjectMapping *objectMapping = nil; @@ -165,14 +179,17 @@ - (id)mapRepresentation:(id)representation atKeyPath:(NSString *)keyPath usingMa } else { // There is more than one mapping present. We are likely mapping secondary key paths to new objects destinationObject = [self objectForRepresentation:representation withMapping:mapping]; + isNewObject = YES; } } } else { destinationObject = [self objectForRepresentation:representation withMapping:mapping]; + isNewObject = YES; } if (mapping && destinationObject) { - BOOL success = [self mapRepresentation:representation toObject:destinationObject atKeyPath:keyPath usingMapping:mapping metadata:self.metadata]; + NSArray *metadataList = [NSArray arrayWithObjects:@{ @"mapping": @{ @"rootKeyPath": keyPath } }, self.metadata, nil]; + BOOL success = [self mapRepresentation:representation toObject:destinationObject isNew:isNewObject atKeyPath:keyPath usingMapping:mapping metadataList:metadataList]; if (success) { return destinationObject; } @@ -206,11 +223,16 @@ - (NSArray *)mapRepresentations:(id)representations atKeyPath:(NSString *)keyPat } } + RKMapperMetadata *mappingData = [RKMapperMetadata new]; + mappingData.rootKeyPath = keyPath; + NSDictionary *metadata = @{ @"mapping": mappingData }; + NSArray *metadataList = [NSArray arrayWithObjects:metadata, self.metadata, nil]; NSMutableArray *mappedObjects = [NSMutableArray arrayWithCapacity:[representations count]]; [objectsToMap enumerateObjectsUsingBlock:^(id mappableObject, NSUInteger index, BOOL *stop) { id destinationObject = [self objectForRepresentation:mappableObject withMapping:mapping]; if (destinationObject) { - BOOL success = [self mapRepresentation:mappableObject toObject:destinationObject atKeyPath:keyPath usingMapping:mapping metadata:@{ @"mapping": @{ @"collectionIndex": @(index) } }]; + mappingData.collectionIndex = index; + BOOL success = [self mapRepresentation:mappableObject toObject:destinationObject isNew:YES atKeyPath:keyPath usingMapping:mapping metadataList:metadataList]; if (success) [mappedObjects addObject:destinationObject]; } *stop = [self isCancelled]; @@ -220,7 +242,7 @@ - (NSArray *)mapRepresentations:(id)representations atKeyPath:(NSString *)keyPat } // The workhorse of this entire process. Emits object loading operations -- (BOOL)mapRepresentation:(id)mappableObject toObject:(id)destinationObject atKeyPath:(NSString *)keyPath usingMapping:(RKMapping *)mapping metadata:(NSDictionary *)metadata +- (BOOL)mapRepresentation:(id)mappableObject toObject:(id)destinationObject isNew:(BOOL)newDestination atKeyPath:(NSString *)keyPath usingMapping:(RKMapping *)mapping metadataList:(NSArray *)metadataList { NSAssert(destinationObject != nil, @"Cannot map without a target object to assign the results to"); NSAssert(mappableObject != nil, @"Cannot map without a collection of attributes"); @@ -228,13 +250,9 @@ - (BOOL)mapRepresentation:(id)mappableObject toObject:(id)destinationObject atKe RKLogDebug(@"Asked to map source object %@ with mapping %@", mappableObject, mapping); - // Merge the metadata for the mapping operation - NSDictionary *mapperMetadata = RKDictionaryByMergingDictionaryWithDictionary(metadata, @{ @"mapping": @{ @"rootKeyPath": keyPath } }); - NSDictionary *operationMetadata = RKDictionaryByMergingDictionaryWithDictionary(self.metadata, mapperMetadata); - - RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:mappableObject destinationObject:destinationObject mapping:mapping]; + RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:mappableObject destinationObject:destinationObject mapping:mapping metadataList:metadataList]; mappingOperation.dataSource = self.mappingOperationDataSource; - mappingOperation.metadata = operationMetadata; + mappingOperation.newDestinationObject = newDestination; if ([self.delegate respondsToSelector:@selector(mapper:willStartMappingOperation:forKeyPath:)]) { [self.delegate mapper:self willStartMappingOperation:mappingOperation forKeyPath:RKDelegateKeyPathFromKeyPath(keyPath)]; } @@ -251,14 +269,17 @@ - (BOOL)mapRepresentation:(id)mappableObject toObject:(id)destinationObject atKe [self.delegate mapper:self didFinishMappingOperation:mappingOperation forKeyPath:RKDelegateKeyPathFromKeyPath(keyPath)]; } - id infoKey = keyPath ?: [NSNull null]; - NSMutableArray *infoForKeyPath = [self.mutableMappingInfo objectForKey:infoKey]; - if (infoForKeyPath) { - [infoForKeyPath addObject:mappingOperation.mappingInfo]; - } else { - infoForKeyPath = [NSMutableArray arrayWithObject:mappingOperation.mappingInfo]; - [self.mutableMappingInfo setValue:infoForKeyPath forKey:infoKey]; + if (mappingOperation.mappingInfo) { + id infoKey = keyPath ?: [NSNull null]; + NSMutableArray *infoForKeyPath = [self.mutableMappingInfo objectForKey:infoKey]; + if (infoForKeyPath) { + [infoForKeyPath addObject:mappingOperation.mappingInfo]; + } else { + infoForKeyPath = [NSMutableArray arrayWithObject:mappingOperation.mappingInfo]; + [self.mutableMappingInfo setValue:infoForKeyPath forKey:infoKey]; + } } + return YES; } } @@ -281,10 +302,19 @@ - (id)objectForRepresentation:(id)representation withMapping:(RKMapping *)mappin } if (objectMapping) { - // Ensure that we are working with a dictionary when we call down into the data source - NSDictionary *representationDictionary = [representation isKindOfClass:[NSDictionary class]] ? representation : @{ [NSNull null]: representation }; - id mappingSourceObject = [[RKMappingSourceObject alloc] initWithObject:representationDictionary parentObject:nil rootObject:representation metadata:self.metadata]; - return [self.mappingOperationDataSource mappingOperation:nil targetObjectForRepresentation:mappingSourceObject withMapping:objectMapping inRelationship:nil]; + id object = nil; + if ([self.mappingOperationDataSource respondsToSelector:@selector(mappingOperation:targetObjectForMapping:inRelationship:)]) + { + object = [self.mappingOperationDataSource mappingOperation:nil targetObjectForMapping:objectMapping inRelationship:nil]; + } + if (object == nil) + { + // Ensure that we are working with a dictionary when we call down into the data source + NSDictionary *representationDictionary = [representation isKindOfClass:[NSDictionary class]] ? representation : @{ [NSNull null]: representation }; + id mappingSourceObject = [[RKMappingSourceObject alloc] initWithObject:representationDictionary parentObject:nil rootObject:representation metadata:self.metadata? @[self.metadata] : nil]; + object = [self.mappingOperationDataSource mappingOperation:nil targetObjectForRepresentation:mappingSourceObject withMapping:objectMapping inRelationship:nil]; + } + return object; } return nil; diff --git a/Code/ObjectMapping/RKMapperOperation_Private.h b/Code/ObjectMapping/RKMapperOperation_Private.h index 6ebe115120..0804dcf5b4 100644 --- a/Code/ObjectMapping/RKMapperOperation_Private.h +++ b/Code/ObjectMapping/RKMapperOperation_Private.h @@ -22,7 +22,7 @@ - (id)mapRepresentation:(id)mappableObject atKeyPath:(NSString *)keyPath usingMapping:(RKMapping *)mapping; - (NSArray *)mapRepresentations:(NSArray *)mappableObjects atKeyPath:(NSString *)keyPath usingMapping:(RKMapping *)mapping; -- (BOOL)mapRepresentation:(id)mappableObject toObject:(id)destinationObject atKeyPath:(NSString *)keyPath usingMapping:(RKMapping *)mapping metadata:(NSDictionary *)metadata; +- (BOOL)mapRepresentation:(id)mappableObject toObject:(id)destinationObject isNew:(BOOL)isNew atKeyPath:(NSString *)keyPath usingMapping:(RKMapping *)mapping metadataList:(NSArray *)metadata; - (id)objectForRepresentation:(id)representation withMapping:(RKMapping *)mapping; @end diff --git a/Code/ObjectMapping/RKMappingOperation.h b/Code/ObjectMapping/RKMappingOperation.h index c5987d9235..3c3de2893a 100644 --- a/Code/ObjectMapping/RKMappingOperation.h +++ b/Code/ObjectMapping/RKMappingOperation.h @@ -222,7 +222,7 @@ 1. `@"root"` - Returns the root node of the representation being mapped. When a large JSON document is being mapped by an instance of `RKMapperOperation` this will point to the parsed JSON document that was used to initialize the operation. 1. `@"parent"` - Returns the direct parent node of the `sourceObject` being mapped or `nil` if the `sourceObject` is itself a root node. */ -@interface RKMappingOperation : NSOperation +@interface RKMappingOperation : NSObject ///--------------------------------------- /// @name Initializing a Mapping Operation @@ -238,6 +238,17 @@ */ - (id)initWithSourceObject:(id)sourceObject destinationObject:(id)destinationObject mapping:(RKMapping *)objectOrDynamicMapping; +/** + Initializes the receiver with a source object, a destination object and an object mapping with which to perform an object mapping, and metadata information to be made available to the mapping. + + @param sourceObject The source object to be mapped. Cannot be `nil`. + @param destinationObject The destination object the results are to be mapped onto. May be `nil`, in which case a new object target object will be obtained from the `dataSource`. + @param objectOrDynamicMapping An instance of `RKObjectMapping` or `RKDynamicMapping` defining how the mapping is to be performed. + @param metadataList A list of objects (usually dictionaries) which provide metadata to the operation, available via the @metadata key in mapping paths. Each object should respond to -valueForKeyPath:, and return nil if the requested key path is not represented in the object (in which case the following object in the list will be consulted). + @return The receiver, initialized with a source object, a destination object, and a mapping. + */ +- (id)initWithSourceObject:(id)sourceObject destinationObject:(id)destinationObject mapping:(RKMapping *)objectOrDynamicMapping metadataList:(NSArray *)metadataList; + ///-------------------------------------- /// @name Accessing Mapping Configuration ///-------------------------------------- @@ -254,6 +265,11 @@ */ @property (nonatomic, strong, readonly) id destinationObject; +/** + Property which is `YES` when the destinationObject was provided from the data source, and `NO` when the destination object was provided externally to the operation. + */ +@property (nonatomic, readonly, getter=isNewDestinationObject) BOOL newDestinationObject; + /** The mapping defining how values contained in the source object should be transformed to the destination object via key-value coding. @@ -269,9 +285,9 @@ @property (nonatomic, strong, readonly) RKObjectMapping *objectMapping; /** - A dictionary of metadata available for mapping in addition to the source object. + A list of metadata objects available for mapping in addition to the source object. */ -@property (nonatomic, copy) NSDictionary *metadata; +@property (nonatomic, strong, readonly) NSArray *metadataList; ///------------------------------------------- /// @name Configuring Delegate and Data Source @@ -305,12 +321,27 @@ */ @property (nonatomic, readonly) RKMappingInfo *mappingInfo; +/** + Property to indicate whether this operation has been cancelled or not. It will be `NO` until `-cancel` is called, after which it will return `YES`. + */ +@property (nonatomic, readonly, getter=isCancelled) BOOL cancelled; + +/** + Cancels the operation, by setting the `cancelled` property to `YES`. Various steps of the process check the `cancelled` property and will abort when it gets set. + */ +- (void)cancel; + ///------------------------- /// @name Performing Mapping ///------------------------- /** - Process all mappable values from the mappable dictionary and assign them to the target object according to the rules expressed in the object mapping definition + Process all mappable values from the mappable dictionary and assign them to the target object according to the rules expressed in the object mapping definition. The error properties need to be checked to see if the operation was successful. + */ +- (void)start; + +/** + Process all mappable values from the mappable dictionary and assign them to the target object according to the rules expressed in the object mapping definition. @param error A pointer to an `NSError` reference to capture any error that occurs during the mapping. May be `nil`. @return A Boolean value indicating if the mapping operation was successful. diff --git a/Code/ObjectMapping/RKMappingOperation.m b/Code/ObjectMapping/RKMappingOperation.m index 438566c400..2ec7771c7d 100644 --- a/Code/ObjectMapping/RKMappingOperation.m +++ b/Code/ObjectMapping/RKMappingOperation.m @@ -37,6 +37,8 @@ #undef RKLogComponent #define RKLogComponent RKlcl_cRestKitObjectMapping +#pragma mark - Mapping utilities + extern NSString * const RKObjectMappingNestingAttributeKeyName; /** @@ -66,13 +68,13 @@ static BOOL RKIsManagedObject(id object) static id RKPrimitiveValueForNilValueOfClass(Class keyValueCodingClass) { if ([keyValueCodingClass isSubclassOfClass:[NSNumber class]]) { - return @(0); + return @0; } else { return nil; } } -// Key comes from: [[self.nestedAttributeSubstitution allKeys] lastObject] AND [[self.nestedAttributeSubstitution allValues] lastObject]; +// Key comes from: nestedAttributeSubstitutionKey AND nestedAttributeSubstitutionValue; NSArray *RKApplyNestingAttributeValueToMappings(NSString *attributeName, id value, NSArray *propertyMappings); NSArray *RKApplyNestingAttributeValueToMappings(NSString *attributeName, id value, NSArray *propertyMappings) { @@ -101,14 +103,17 @@ static id RKPrimitiveValueForNilValueOfClass(Class keyValueCodingClass) } // Returns YES if there is a value present for at least one key path in the given collection -static BOOL RKObjectContainsValueForKeyPaths(id representation, NSArray *keyPaths) +static BOOL RKObjectContainsValueForMappings(id representation, NSArray *propertyMappings) { - for (NSString *keyPath in keyPaths) { - if ([representation valueForKeyPath:keyPath]) return YES; + for (RKPropertyMapping *mapping in propertyMappings) { + NSString *keyPath = mapping.sourceKeyPath; + if (keyPath && [representation valueForKeyPath:keyPath]) return YES; } return NO; } +#pragma mark - Metadata utilities + static NSString *const RKMetadataKey = @"@metadata"; static NSString *const RKMetadataKeyPathPrefix = @"@metadata."; static NSString *const RKParentKey = @"@parent"; @@ -118,50 +123,186 @@ static BOOL RKObjectContainsValueForKeyPaths(id representation, NSArray *keyPath static NSString *const RKSelfKey = @"self"; static NSString *const RKSelfKeyPathPrefix = @"self."; -@interface RKMappingSourceObject : NSProxy -- (id)initWithObject:(id)object parentObject:(id)parentObject rootObject:(id)rootObject metadata:(NSDictionary *)metadata; +/** + Inserts up to two objects a the start of the metadata list. metadata1 will be at the front if both are provided. + */ +static NSArray *RKInsertInMetadataList(NSArray *list, id metadata1, id metadata2) +{ + if (metadata1 == nil && metadata2 == nil) + return list; + NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray:list]; + if (metadata2) + [newArray insertObject:metadata2 atIndex:0]; + if (metadata1) + [newArray insertObject:metadata1 atIndex:0]; + return newArray; +} + +@interface RKMappingSourceObject : NSObject +- (id)initWithObject:(id)object parentObject:(id)parentObject rootObject:(id)rootObject metadata:(NSArray *)metadata; +- (id)metadataValueForKey:(NSString *)key; +- (id)metadataValueForKeyPath:(NSString *)keyPath; +@end + +/** + Class used in the single case of RKMappingSourceObject needing to return a single object + for the "@metadata" key, which a special implementation of -valueForKeyPath: + to iterate over the list of metadata dictionaries (which RKMappingSourceObject usually does). + This usually only happens from the parentObjectForRelationshipMapping: implementation, but + in case it does this class provides the implementation. + */ +@interface RKMetadataWrapper : NSObject +- (id)initWithMappingSource:(RKMappingSourceObject *)source; +@property (nonatomic, strong) RKMappingSourceObject *mappingSource; +@end + +@implementation RKMetadataWrapper + +- (id)initWithMappingSource:(RKMappingSourceObject *)source { + if (self = [super init]) { + self.mappingSource = source; + } + return self; +} + +- (id)valueForKey:(NSString *)key +{ + return [self.mappingSource metadataValueForKey:key]; +} +- (id)valueForKeyPath:(NSString *)keyPath +{ + return [self.mappingSource metadataValueForKeyPath:keyPath]; +} +@end + +/** + Class meant to represent parts of the "mapping" sub-dictionary of the "@metadata" keys, but + being more efficient to create than actual NSDictionary instances. We can add any object properties + to this class, and if non-nil that value will be used, otherwise it is a passthrough. + */ +@interface RKMappingMetadata : NSObject +@property (nonatomic) BOOL inValueForKeyPath; +@property (nonatomic) id parentObject; +@end + +@implementation RKMappingMetadata + +- (id)valueForKeyPath:(NSString *)keyPath +{ + static NSString *mappingPrefix = @"mapping."; + + /* We only allow paths with a "mapping." prefix, to simulate being a nested object */ + if ([keyPath hasPrefix:mappingPrefix]) { + self.inValueForKeyPath = YES; + id value = [super valueForKeyPath:[keyPath substringFromIndex:[mappingPrefix length]]]; + self.inValueForKeyPath = NO; + return value; + } + + return nil; +} + +/* Only return values from valueForKey: if we are being routed from valueForKeyPath:. This + avoids us from returning value values from say "@metadata.collectionIndex" without the mapping prefix. + */ +- (id)valueForKey:(NSString *)key +{ + return self.inValueForKeyPath? [super valueForKey:key] : nil; +} + +/* Return nil for any unknown keys, so the next object in the metadata list gets checked */ +- (id)valueForUndefinedKey:(NSString *)key +{ + return nil; +} + @end +/** + Subclass of RKMappingMetadata for use for holding the collectionIndex during a to-many mapping operation. + Needs to be a subclass since the scalar property cannot return nil from valueForKey, so this can only + be used when the collectionIndex is definitely set. + */ +@interface RKMappingIndexMetadata : RKMappingMetadata +@property (nonatomic) NSUInteger collectionIndex; +@end + +@implementation RKMappingIndexMetadata +@end + + @interface RKMappingSourceObject () @property (nonatomic, strong) id object; @property (nonatomic, strong) id parentObject; @property (nonatomic, strong) id rootObject; -@property (nonatomic, strong) NSDictionary *metadata; +@property (nonatomic, strong) NSArray *metadataList; @end @implementation RKMappingSourceObject -- (id)initWithObject:(id)object parentObject:(id)parentObject rootObject:(id)rootObject metadata:(NSDictionary *)metadata +- (id)initWithObject:(id)object parentObject:(id)parentObject rootObject:(id)rootObject metadata:(NSArray *)metadata { self.object = object; self.parentObject = parentObject; self.rootObject = rootObject; - self.metadata = metadata; + self.metadataList = metadata; return self; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { - return [self.object methodSignatureForSelector:selector]; + return [_object methodSignatureForSelector:selector]; } - (void)forwardInvocation:(NSInvocation *)invocation { - [invocation invokeWithTarget:self.object]; + [invocation invokeWithTarget:_object]; +} + +- (id)forwardingTargetForSelector:(SEL)aSelector +{ + return _object; +} + +- (id)metadataValueForKey:(NSString *)key +{ + for (NSDictionary *dict in self.metadataList) + { + id val = [dict valueForKey:key]; + if (val != nil) return val; + } + + return nil; +} + +- (id)metadataValueForKeyPath:(NSString *)keyPath +{ + for (NSDictionary *dict in self.metadataList) + { + id val = [dict valueForKeyPath:keyPath]; + if (val != nil) return val; + } + + return nil; } - (id)valueForKey:(NSString *)key { - if ([key isEqualToString:RKMetadataKey]) { - return self.metadata; + /* Using firstChar as a small performance enhancement -- one check can avoid several isEqual: calls */ + unichar firstChar = [key length] > 0 ? [key characterAtIndex:0] : 0; + + if (firstChar == 's' && [key isEqualToString:RKSelfKey]) { + return _object; + } else if (firstChar != '@') { + return [_object valueForKey:key]; + } else if ([key isEqualToString:RKMetadataKey]) { + return [[RKMetadataWrapper alloc] initWithMappingSource:self]; } else if ([key isEqualToString:RKParentKey]) { return self.parentObject; } else if ([key isEqualToString:RKRootKey]) { return self.rootObject; - } else if ([key isEqualToString:RKSelfKey]) { - return self.object; } else { - return [self.object valueForKey:key]; + return [_object valueForKey:key]; } } @@ -170,35 +311,63 @@ - (id)valueForKey:(NSString *)key */ - (id)valueForKeyPath:(NSString *)keyPath { - if ([keyPath hasPrefix:RKMetadataKeyPathPrefix]) { + /* Using firstChar as a small performance enhancement -- one check can avoid several hasPrefix calls */ + unichar firstChar = [keyPath length] > 0 ? [keyPath characterAtIndex:0] : 0; + + if (firstChar == 's' && [keyPath hasPrefix:RKSelfKeyPathPrefix]) { + NSString *selfKeyPath = [keyPath substringFromIndex:[RKSelfKeyPathPrefix length]]; + return [_object valueForKeyPath:selfKeyPath]; + } else if (firstChar != '@') { + return [_object valueForKeyPath:keyPath]; + } else if ([keyPath hasPrefix:RKMetadataKeyPathPrefix]) { NSString *metadataKeyPath = [keyPath substringFromIndex:[RKMetadataKeyPathPrefix length]]; - return [self.metadata valueForKeyPath:metadataKeyPath]; + return [self metadataValueForKeyPath:metadataKeyPath]; } else if ([keyPath hasPrefix:RKParentKeyPathPrefix]) { NSString *parentKeyPath = [keyPath substringFromIndex:[RKParentKeyPathPrefix length]]; return [self.parentObject valueForKeyPath:parentKeyPath]; } else if ([keyPath hasPrefix:RKRootKeyPathPrefix]) { NSString *rootKeyPath = [keyPath substringFromIndex:[RKRootKeyPathPrefix length]]; return [self.rootObject valueForKeyPath:rootKeyPath]; - } else if ([keyPath hasPrefix:RKSelfKeyPathPrefix]) { - NSString *selfKeyPath = [keyPath substringFromIndex:[RKSelfKeyPathPrefix length]]; - return [self.object valueForKeyPath:selfKeyPath]; } else { - return [self.object valueForKeyPath:keyPath]; + return [_object valueForKeyPath:keyPath]; } } - (NSString *)description { - return [NSString stringWithFormat:@"%@ (%@)", [self.object description], self.metadata]; + return [NSString stringWithFormat:@"%@ (%@)", [self.object description], self.metadataList]; } - (Class)class { - return [self.object class]; + return [_object class]; +} + +- (BOOL)isKindOfClass:(Class)aClass +{ + return [_object isKindOfClass:aClass]; +} + +- (BOOL)respondsToSelector:(SEL)aSelector +{ + return [_object respondsToSelector:aSelector]; +} + +- (BOOL)conformsToProtocol:(Protocol *)aProtocol +{ + return [_object conformsToProtocol:aProtocol]; +} + +- (Class)rk_classForPropertyAtKeyPath:(NSString *)keyPath isPrimitive:(BOOL *)isPrimitive +{ + return [_object rk_classForPropertyAtKeyPath:keyPath isPrimitive:isPrimitive]; } @end + +#pragma mark - RKMappingInfo + @interface RKMappingInfo () @property (nonatomic, assign, readwrite) NSUInteger collectionIndex; @property (nonatomic, strong) NSMutableSet *mutablePropertyMappings; @@ -260,22 +429,38 @@ - (id)objectForKeyedSubscript:(id)key @end +#pragma mark - RKMappingOperation + @interface RKMappingOperation () @property (nonatomic, strong, readwrite) RKMapping *mapping; @property (nonatomic, strong, readwrite) id sourceObject; @property (nonatomic, strong, readwrite) id parentSourceObject; @property (nonatomic, strong, readwrite) id rootSourceObject; @property (nonatomic, strong, readwrite) id destinationObject; -@property (nonatomic, strong) NSDictionary *nestedAttributeSubstitution; +@property (nonatomic, strong, readwrite) NSArray *metadataList; +@property (nonatomic, strong) NSString *nestedAttributeSubstitutionKey; +@property (nonatomic, strong) id nestedAttributeSubstitutionValue; @property (nonatomic, strong, readwrite) NSError *error; @property (nonatomic, strong, readwrite) RKObjectMapping *objectMapping; // The concrete mapping @property (nonatomic, strong) NSArray *nestedAttributeMappings; +@property (nonatomic, strong) NSArray *simpleAttributeMappings; +@property (nonatomic, strong) NSArray *keyPathAttributeMappings; +@property (nonatomic, strong) NSArray *relationshipMappings; @property (nonatomic, strong) RKMappingInfo *mappingInfo; +@property (nonatomic, getter=isCancelled) BOOL cancelled; +@property (nonatomic) BOOL collectsMappingInfo; +@property (nonatomic) BOOL shouldSetUnchangedValues; +@property (nonatomic, readwrite, getter=isNewDestinationObject) BOOL newDestinationObject; @end @implementation RKMappingOperation - (id)initWithSourceObject:(id)sourceObject destinationObject:(id)destinationObject mapping:(RKMapping *)objectOrDynamicMapping +{ + return [self initWithSourceObject:sourceObject destinationObject:destinationObject mapping:objectOrDynamicMapping metadataList:nil]; +} + +- (id)initWithSourceObject:(id)sourceObject destinationObject:(id)destinationObject mapping:(RKMapping *)objectOrDynamicMapping metadataList:(NSArray *)metadataList { NSAssert(sourceObject != nil, @"Cannot perform a mapping operation without a sourceObject object"); NSAssert(objectOrDynamicMapping != nil, @"Cannot perform a mapping operation without a mapping"); @@ -286,6 +471,7 @@ - (id)initWithSourceObject:(id)sourceObject destinationObject:(id)destinationObj self.rootSourceObject = sourceObject; self.destinationObject = destinationObject; self.mapping = objectOrDynamicMapping; + self.metadataList = metadataList; } return self; @@ -294,16 +480,20 @@ - (id)initWithSourceObject:(id)sourceObject destinationObject:(id)destinationObj - (id)parentObjectForRelationshipMapping:(RKRelationshipMapping *)mapping { id parentSourceObject = self.sourceObject; + NSString *sourceKeyPath = mapping.sourceKeyPath; - NSArray *sourceKeyComponents = [mapping.sourceKeyPath componentsSeparatedByString:@"."]; - if (sourceKeyComponents.count > 1) + NSRange lastDotRange = [sourceKeyPath rangeOfString:@"." options:NSBackwardsSearch|NSLiteralSearch]; + if (lastDotRange.length > 0) { - for (NSString *key in [sourceKeyComponents subarrayWithRange:NSMakeRange(0, sourceKeyComponents.count - 1)]) + NSString *parentKey = [sourceKeyPath substringToIndex:lastDotRange.location]; + id rootObject = self.rootSourceObject; + NSArray *metadata = self.metadataList; + for (NSString *key in [parentKey componentsSeparatedByString:@"."]) { parentSourceObject = [[RKMappingSourceObject alloc] initWithObject:[parentSourceObject valueForKey:key] parentObject:parentSourceObject - rootObject:self.rootSourceObject - metadata:self.metadata]; + rootObject:rootObject + metadata:metadata]; } } @@ -323,26 +513,45 @@ - (id)destinationObjectForMappingRepresentation:(id)representation parentReprese concreteMapping = (RKObjectMapping *)mapping; } - NSDictionary *dictionaryRepresentation = [representation isKindOfClass:[NSDictionary class]] ? representation : @{ [NSNull null] : representation }; - NSDictionary *metadata = RKDictionaryByMergingDictionaryWithDictionary(self.metadata, @{ @"mapping": @{ @"parentObject": (self.destinationObject ?: [NSNull null]) } }); - RKMappingSourceObject *sourceObject = [[RKMappingSourceObject alloc] initWithObject:dictionaryRepresentation parentObject:parentRepresentation rootObject:self.rootSourceObject metadata:metadata]; - return [self.dataSource mappingOperation:self targetObjectForRepresentation:(NSDictionary *)sourceObject withMapping:concreteMapping inRelationship:relationshipMapping]; + id destinationObject = nil; + id dataSource = self.dataSource; + if ([dataSource respondsToSelector:@selector(mappingOperation:targetObjectForMapping:inRelationship:)]) + { + destinationObject = [dataSource mappingOperation:self targetObjectForMapping:concreteMapping inRelationship:relationshipMapping]; + } + + if (destinationObject == nil) + { + NSDictionary *dictionaryRepresentation = [representation isKindOfClass:[NSDictionary class]] ? representation : @{ [NSNull null] : representation }; + RKMappingMetadata *parentMetadata = [RKMappingMetadata new]; + parentMetadata.parentObject = self.destinationObject ?: [NSNull null]; + NSArray *metadata = RKInsertInMetadataList(self.metadataList, parentMetadata, nil); + RKMappingSourceObject *sourceObject = [[RKMappingSourceObject alloc] initWithObject:dictionaryRepresentation parentObject:parentRepresentation rootObject:self.rootSourceObject metadata:metadata]; + destinationObject = [dataSource mappingOperation:self targetObjectForRepresentation:(NSDictionary *)sourceObject withMapping:concreteMapping inRelationship:relationshipMapping]; + } + + return destinationObject; } - (BOOL)validateValue:(id *)value atKeyPath:(NSString *)keyPath { BOOL success = YES; - if (self.objectMapping.performsKeyValueValidation && [self.destinationObject respondsToSelector:@selector(validateValue:forKeyPath:error:)]) { - NSError *validationError; - success = [self.destinationObject validateValue:value forKeyPath:keyPath error:&validationError]; - if (!success) { - self.error = validationError; - if (validationError) { - RKLogError(@"Validation failed while mapping attribute at key path '%@' to value %@. Error: %@", keyPath, *value, [validationError localizedDescription]); - RKLogValidationError(validationError); - } else { - RKLogWarning(@"Destination object %@ rejected attribute value %@ for keyPath %@. Skipping...", self.destinationObject, *value, keyPath); + if (self.objectMapping.performsKeyValueValidation) { + id destinationObject = self.destinationObject; + + if ([destinationObject respondsToSelector:@selector(validateValue:forKeyPath:error:)]) { + NSError *validationError; + success = [destinationObject validateValue:value forKeyPath:keyPath error:&validationError]; + if (!success) { + self.error = validationError; + if (validationError) { + RKLogError(@"Validation failed while mapping attribute at key path '%@' to value. Error: %@", keyPath, [validationError localizedDescription]); + RKLogValidationError(validationError); + } else { + RKLogWarning(@"Destination object %@ rejected attribute value for keyPath %@. Skipping...", self.destinationObject, keyPath); + } + RKLogDebug(@"(Value for key path '%@': %@)", keyPath, *value); } } } @@ -357,7 +566,7 @@ - (BOOL)shouldSetValue:(id *)value forKeyPath:(NSString *)keyPath usingMapping:( } // Always set the properties - if ([self.dataSource respondsToSelector:@selector(mappingOperationShouldSetUnchangedValues:)] && [self.dataSource mappingOperationShouldSetUnchangedValues:self]) { + if (self.shouldSetUnchangedValues) { return [self validateValue:value atKeyPath:keyPath]; } @@ -392,44 +601,65 @@ - (BOOL)shouldSetValue:(id *)value forKeyPath:(NSString *)keyPath usingMapping:( - (NSArray *)applyNestingToMappings:(NSArray *)propertyMappings { - NSString *attributeName = [[self.nestedAttributeSubstitution allKeys] lastObject]; - id value = [[self.nestedAttributeSubstitution allValues] lastObject]; - return self.nestedAttributeSubstitution ? RKApplyNestingAttributeValueToMappings(attributeName, value, propertyMappings) : propertyMappings; + if (self.nestedAttributeSubstitutionKey == nil) return propertyMappings; + + return RKApplyNestingAttributeValueToMappings(self.nestedAttributeSubstitutionKey, self.nestedAttributeSubstitutionValue, propertyMappings); +} + +- (void)cacheMappingsIfNeeded +{ + if (!_nestedAttributeMappings) + { + RKObjectMapping *mapping = self.objectMapping; + + if (self.nestedAttributeSubstitutionKey == nil) { + _relationshipMappings = mapping.relationshipMappings; + _nestedAttributeMappings = mapping.attributeMappings; + _simpleAttributeMappings = mapping.keyAttributeMappings; + _keyPathAttributeMappings = mapping.keyPathAttributeMappings; + } + else { + _nestedAttributeMappings = [self applyNestingToMappings:mapping.attributeMappings]; + _relationshipMappings = [self applyNestingToMappings:mapping.relationshipMappings]; + NSMutableArray *simpleList = [[NSMutableArray alloc] initWithCapacity:[_nestedAttributeMappings count]]; + NSMutableArray *keyPathList = [[NSMutableArray alloc] initWithCapacity:[_nestedAttributeMappings count]]; + + // The nested substitution may have changed which properties are simple vs keyPath, so we have to + // re-check based on the nesting result. + for (RKPropertyMapping *mapping in _nestedAttributeMappings) { + BOOL isSimple = [mapping.sourceKeyPath rangeOfString:@"." options:NSLiteralSearch].length == 0; + NSMutableArray *arrayToAdd = isSimple? simpleList : keyPathList; + [arrayToAdd addObject:mapping]; + } + + _simpleAttributeMappings = simpleList; + _keyPathAttributeMappings = keyPathList; + } + } } - (NSArray *)nestedAttributeMappings { - if (!_nestedAttributeMappings) _nestedAttributeMappings = [self applyNestingToMappings:self.objectMapping.attributeMappings]; + [self cacheMappingsIfNeeded]; return _nestedAttributeMappings; } - (NSArray *)simpleAttributeMappings { - NSMutableArray *mappings = [NSMutableArray array]; - for (RKAttributeMapping *mapping in self.nestedAttributeMappings) { - if ([mapping.sourceKeyPath rangeOfString:@"."].location == NSNotFound) { - [mappings addObject:mapping]; - } - } - - return mappings; + [self cacheMappingsIfNeeded]; + return _simpleAttributeMappings; } - (NSArray *)keyPathAttributeMappings { - NSMutableArray *mappings = [NSMutableArray array]; - for (RKAttributeMapping *mapping in self.nestedAttributeMappings) { - if ([mapping.sourceKeyPath rangeOfString:@"."].location != NSNotFound) { - [mappings addObject:mapping]; - } - } - - return mappings; + [self cacheMappingsIfNeeded]; + return _keyPathAttributeMappings; } - (NSArray *)relationshipMappings { - return [self applyNestingToMappings:self.objectMapping.relationshipMappings]; + [self cacheMappingsIfNeeded]; + return _relationshipMappings; } - (BOOL)transformValue:(id)inputValue toValue:(__autoreleasing id *)outputValue withPropertyMapping:(RKPropertyMapping *)propertyMapping error:(NSError *__autoreleasing *)error @@ -456,46 +686,52 @@ - (BOOL)applyAttributeMapping:(RKAttributeMapping *)attributeMapping withValue:( NSError *error = nil; if (! [self transformValue:value toValue:&transformedValue withPropertyMapping:attributeMapping error:&error]) return NO; - if ([self.delegate respondsToSelector:@selector(mappingOperation:didFindValue:forKeyPath:mapping:)]) { - [self.delegate mappingOperation:self didFindValue:value forKeyPath:attributeMapping.sourceKeyPath mapping:attributeMapping]; + NSString *destinationKeyPath = attributeMapping.destinationKeyPath; + id destinationObject = self.destinationObject; + id delegate = self.delegate; + + if ([delegate respondsToSelector:@selector(mappingOperation:didFindValue:forKeyPath:mapping:)]) { + [delegate mappingOperation:self didFindValue:value forKeyPath:attributeMapping.sourceKeyPath mapping:attributeMapping]; } - RKLogTrace(@"Mapping attribute value keyPath '%@' to '%@'", attributeMapping.sourceKeyPath, attributeMapping.destinationKeyPath); + RKLogTrace(@"Mapping attribute value keyPath '%@' to '%@'", attributeMapping.sourceKeyPath, destinationKeyPath); // If we have a nil value for a primitive property, we need to coerce it into a KVC usable value or bail out - if (transformedValue == nil && RKPropertyInspectorIsPropertyAtKeyPathOfObjectPrimitive(attributeMapping.destinationKeyPath, self.destinationObject)) { - RKLogDebug(@"Detected `nil` value transformation for primitive property at keyPath '%@'", attributeMapping.destinationKeyPath); - transformedValue = RKPrimitiveValueForNilValueOfClass([self.objectMapping classForKeyPath:attributeMapping.destinationKeyPath]); + if (transformedValue == nil && RKPropertyInspectorIsPropertyAtKeyPathOfObjectPrimitive(destinationKeyPath, destinationObject)) { + RKLogDebug(@"Detected `nil` value transformation for primitive property at keyPath '%@'", destinationKeyPath); + transformedValue = RKPrimitiveValueForNilValueOfClass([self.objectMapping classForKeyPath:destinationKeyPath]); if (! transformedValue) { - RKLogTrace(@"Skipped mapping of attribute value from keyPath '%@ to keyPath '%@' -- Unable to transform `nil` into primitive value representation", attributeMapping.sourceKeyPath, attributeMapping.destinationKeyPath); + RKLogTrace(@"Skipped mapping of attribute value from keyPath '%@ to keyPath '%@' -- Unable to transform `nil` into primitive value representation", attributeMapping.sourceKeyPath, destinationKeyPath); return NO; } } - RKSetIntermediateDictionaryValuesOnObjectForKeyPath(self.destinationObject, attributeMapping.destinationKeyPath); + RKSetIntermediateDictionaryValuesOnObjectForKeyPath(destinationObject, destinationKeyPath); // Ensure that the value is different - if ([self shouldSetValue:&transformedValue forKeyPath:attributeMapping.destinationKeyPath usingMapping:attributeMapping]) { - RKLogTrace(@"Mapped attribute value from keyPath '%@' to '%@'. Value: %@", attributeMapping.sourceKeyPath, attributeMapping.destinationKeyPath, transformedValue); + if ([self shouldSetValue:&transformedValue forKeyPath:destinationKeyPath usingMapping:attributeMapping]) { + RKLogTrace(@"Mapped attribute value from keyPath '%@' to '%@'. Value: %@", attributeMapping.sourceKeyPath, destinationKeyPath, transformedValue); - if (attributeMapping.destinationKeyPath) { - [self.destinationObject setValue:transformedValue forKeyPath:attributeMapping.destinationKeyPath]; + if (destinationKeyPath) { + [destinationObject setValue:transformedValue forKeyPath:destinationKeyPath]; } else { - if ([self.destinationObject isKindOfClass:[NSMutableDictionary class]] && [transformedValue isKindOfClass:[NSDictionary class]]) { - [self.destinationObject setDictionary:transformedValue]; + if ([destinationObject isKindOfClass:[NSMutableDictionary class]] && [transformedValue isKindOfClass:[NSDictionary class]]) { + [destinationObject setDictionary:transformedValue]; } else { - [NSException raise:NSInvalidArgumentException format:@"Unable to set value for destination object of type '%@': Can only directly set destination object for `NSMutableDictionary` targets. (transformedValue=%@)", [self.destinationObject class], transformedValue]; + [NSException raise:NSInvalidArgumentException format:@"Unable to set value for destination object of type '%@': Can only directly set destination object for `NSMutableDictionary` targets. (transformedValue=%@)", [destinationObject class], transformedValue]; } } - if ([self.delegate respondsToSelector:@selector(mappingOperation:didSetValue:forKeyPath:usingMapping:)]) { - [self.delegate mappingOperation:self didSetValue:transformedValue forKeyPath:attributeMapping.destinationKeyPath usingMapping:attributeMapping]; + if ([delegate respondsToSelector:@selector(mappingOperation:didSetValue:forKeyPath:usingMapping:)]) { + [delegate mappingOperation:self didSetValue:transformedValue forKeyPath:destinationKeyPath usingMapping:attributeMapping]; } } else { - RKLogTrace(@"Skipped mapping of attribute value from keyPath '%@ to keyPath '%@' -- value is unchanged (%@)", attributeMapping.sourceKeyPath, attributeMapping.destinationKeyPath, transformedValue); - if ([self.delegate respondsToSelector:@selector(mappingOperation:didNotSetUnchangedValue:forKeyPath:usingMapping:)]) { - [self.delegate mappingOperation:self didNotSetUnchangedValue:transformedValue forKeyPath:attributeMapping.destinationKeyPath usingMapping:attributeMapping]; + RKLogTrace(@"Skipped mapping of attribute value from keyPath '%@ to keyPath '%@' -- value is unchanged (%@)", attributeMapping.sourceKeyPath, destinationKeyPath, transformedValue); + if ([delegate respondsToSelector:@selector(mappingOperation:didNotSetUnchangedValue:forKeyPath:usingMapping:)]) { + [delegate mappingOperation:self didNotSetUnchangedValue:transformedValue forKeyPath:destinationKeyPath usingMapping:attributeMapping]; } } - [self.mappingInfo addPropertyMapping:attributeMapping]; + if (_collectsMappingInfo) { + [self.mappingInfo addPropertyMapping:attributeMapping]; + } return YES; } @@ -503,34 +739,41 @@ - (BOOL)applyAttributeMapping:(RKAttributeMapping *)attributeMapping withValue:( - (BOOL)applyAttributeMappings:(NSArray *)attributeMappings { // If we have a nesting substitution value, we have already succeeded - BOOL appliedMappings = (self.nestedAttributeSubstitution != nil); + BOOL appliedMappings = (self.nestedAttributeSubstitutionKey != nil); if (!self.objectMapping.performsKeyValueValidation) { RKLogDebug(@"Key-value validation is disabled for mapping, skipping..."); } + id sourceObject = self.sourceObject; + for (RKAttributeMapping *attributeMapping in attributeMappings) { if ([self isCancelled]) return NO; - if ([attributeMapping.sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName] || [attributeMapping.destinationKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) { - RKLogTrace(@"Skipping attribute mapping for special keyPath '%@'", attributeMapping.sourceKeyPath); + NSString *sourceKeyPath = attributeMapping.sourceKeyPath; + NSString *destinationKeyPath = attributeMapping.destinationKeyPath; + if ([sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName] || [destinationKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) { + RKLogTrace(@"Skipping attribute mapping for special keyPath '%@'", sourceKeyPath); continue; } - id value = (attributeMapping.sourceKeyPath == nil) ? [self.sourceObject valueForKey:@"self"] : [self.sourceObject valueForKeyPath:attributeMapping.sourceKeyPath]; + id value = (sourceKeyPath == nil) ? [sourceObject valueForKey:@"self"] : [sourceObject valueForKeyPath:sourceKeyPath]; if ([self applyAttributeMapping:attributeMapping withValue:value]) { appliedMappings = YES; } else { - if ([self.delegate respondsToSelector:@selector(mappingOperation:didNotFindValueForKeyPath:mapping:)]) { - [self.delegate mappingOperation:self didNotFindValueForKeyPath:attributeMapping.sourceKeyPath mapping:attributeMapping]; + id delegate = self.delegate; + RKObjectMapping *objectMapping = self.objectMapping; + + if ([delegate respondsToSelector:@selector(mappingOperation:didNotFindValueForKeyPath:mapping:)]) { + [delegate mappingOperation:self didNotFindValueForKeyPath:sourceKeyPath mapping:attributeMapping]; } - RKLogTrace(@"Did not find mappable attribute value keyPath '%@'", attributeMapping.sourceKeyPath); + RKLogTrace(@"Did not find mappable attribute value keyPath '%@'", sourceKeyPath); // Optionally set the default value for missing values - if (self.objectMapping.assignsDefaultValueForMissingAttributes) { - [self.destinationObject setValue:[self.objectMapping defaultValueForAttribute:attributeMapping.destinationKeyPath] - forKeyPath:attributeMapping.destinationKeyPath]; - RKLogTrace(@"Setting nil for missing attribute value at keyPath '%@'", attributeMapping.sourceKeyPath); + if (objectMapping.assignsDefaultValueForMissingAttributes) { + [self.destinationObject setValue:[objectMapping defaultValueForAttribute:destinationKeyPath] + forKeyPath:destinationKeyPath]; + RKLogTrace(@"Setting nil for missing attribute value at keyPath '%@'", sourceKeyPath); } } @@ -541,27 +784,30 @@ - (BOOL)applyAttributeMappings:(NSArray *)attributeMappings return appliedMappings; } -- (BOOL)mapNestedObject:(id)anObject toObject:(id)anotherObject withRelationshipMapping:(RKRelationshipMapping *)relationshipMapping metadata:(NSDictionary *)metadata +- (BOOL)mapNestedObject:(id)anObject toObject:(id)anotherObject parent:(id)parentSourceObject withRelationshipMapping:(RKRelationshipMapping *)relationshipMapping metadataList:(NSArray *)metadataList { NSAssert(anObject, @"Cannot map nested object without a nested source object"); NSAssert(anotherObject, @"Cannot map nested object without a destination object"); NSAssert(relationshipMapping, @"Cannot map a nested object relationship without a relationship mapping"); RKLogTrace(@"Performing nested object mapping using mapping %@ for data: %@", relationshipMapping, anObject); - NSDictionary *subOperationMetadata = RKDictionaryByMergingDictionaryWithDictionary(self.metadata, metadata); - RKMappingOperation *subOperation = [[RKMappingOperation alloc] initWithSourceObject:anObject destinationObject:anotherObject mapping:relationshipMapping.mapping]; + RKMappingOperation *subOperation = [[RKMappingOperation alloc] initWithSourceObject:anObject destinationObject:anotherObject mapping:relationshipMapping.mapping metadataList:metadataList]; subOperation.dataSource = self.dataSource; subOperation.delegate = self.delegate; - subOperation.metadata = subOperationMetadata; - subOperation.parentSourceObject = [self parentObjectForRelationshipMapping:relationshipMapping]; + subOperation.parentSourceObject = parentSourceObject; subOperation.rootSourceObject = self.rootSourceObject; + subOperation.newDestinationObject = YES; [subOperation start]; if (subOperation.error) { RKLogWarning(@"WARNING: Failed mapping nested object: %@", [subOperation.error localizedDescription]); - } else { - [self.mappingInfo addPropertyMapping:relationshipMapping]; - [self.mappingInfo addMappingInfo:subOperation.mappingInfo forRelationshipMapping:relationshipMapping]; + } else if (self.collectsMappingInfo) { + RKMappingInfo *mappingInfo = self.mappingInfo; + RKMappingInfo *subMappingInfo = subOperation.mappingInfo; + [mappingInfo addPropertyMapping:relationshipMapping]; + if (subMappingInfo) { + [mappingInfo addMappingInfo:subMappingInfo forRelationshipMapping:relationshipMapping]; + } } return YES; @@ -570,9 +816,10 @@ - (BOOL)mapNestedObject:(id)anObject toObject:(id)anotherObject withRelationship - (BOOL)applyReplaceAssignmentPolicyForRelationshipMapping:(RKRelationshipMapping *)relationshipMapping { if (relationshipMapping.assignmentPolicy == RKReplaceAssignmentPolicy) { - if ([self.dataSource respondsToSelector:@selector(mappingOperation:deleteExistingValueOfRelationshipWithMapping:error:)]) { + id dataSource = self.dataSource; + if ([dataSource respondsToSelector:@selector(mappingOperation:deleteExistingValueOfRelationshipWithMapping:error:)]) { NSError *error = nil; - BOOL success = [self.dataSource mappingOperation:self deleteExistingValueOfRelationshipWithMapping:relationshipMapping error:&error]; + BOOL success = [dataSource mappingOperation:self deleteExistingValueOfRelationshipWithMapping:relationshipMapping error:&error]; if (! success) { RKLogError(@"Failed to delete existing value of relationship mapped with RKReplaceAssignmentPolicy: %@", error); self.error = error; @@ -588,8 +835,15 @@ - (BOOL)applyReplaceAssignmentPolicyForRelationshipMapping:(RKRelationshipMappin - (BOOL)mapOneToOneRelationshipWithValue:(id)value mapping:(RKRelationshipMapping *)relationshipMapping { + static dispatch_once_t onceToken; + static NSDictionary *noIndexMetadata; + dispatch_once(&onceToken, ^{ + noIndexMetadata = @{ @"mapping" : @{ @"collectionIndex" : [NSNull null] } }; + }); + // One to one relationship - RKLogDebug(@"Mapping one to one relationship value at keyPath '%@' to '%@'", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath); + NSString *destinationKeyPath = relationshipMapping.destinationKeyPath; + RKLogDebug(@"Mapping one to one relationship value at keyPath '%@' to '%@'", relationshipMapping.sourceKeyPath, destinationKeyPath); if (relationshipMapping.assignmentPolicy == RKUnionAssignmentPolicy) { NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Invalid assignment policy: cannot union a one-to-one relationship." }; @@ -603,19 +857,21 @@ - (BOOL)mapOneToOneRelationshipWithValue:(id)value mapping:(RKRelationshipMappin RKLogDebug(@"Mapping %@ declined mapping for representation %@: returned `nil` destination object.", relationshipMapping.mapping, destinationObject); return NO; } - [self mapNestedObject:value toObject:destinationObject withRelationshipMapping:relationshipMapping metadata:@{ @"mapping": @{ @"collectionIndex": [NSNull null] } }]; + + NSArray *subOperationMetadata = RKInsertInMetadataList(self.metadataList, noIndexMetadata, nil); + [self mapNestedObject:value toObject:destinationObject parent:parentSourceObject withRelationshipMapping:relationshipMapping metadataList:subOperationMetadata]; // If the relationship has changed, set it - if ([self shouldSetValue:&destinationObject forKeyPath:relationshipMapping.destinationKeyPath usingMapping:relationshipMapping]) { + if ([self shouldSetValue:&destinationObject forKeyPath:destinationKeyPath usingMapping:relationshipMapping]) { if (! [self applyReplaceAssignmentPolicyForRelationshipMapping:relationshipMapping]) { return NO; } - RKLogTrace(@"Mapped relationship object from keyPath '%@' to '%@'. Value: %@", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath, destinationObject); - [self.destinationObject setValue:destinationObject forKeyPath:relationshipMapping.destinationKeyPath]; + RKLogTrace(@"Mapped relationship object from keyPath '%@' to '%@'. Value: %@", relationshipMapping.sourceKeyPath, destinationKeyPath, destinationObject); + [self.destinationObject setValue:destinationObject forKeyPath:destinationKeyPath]; } else { if ([self.delegate respondsToSelector:@selector(mappingOperation:didNotSetUnchangedValue:forKeyPath:usingMapping:)]) { - [self.delegate mappingOperation:self didNotSetUnchangedValue:destinationObject forKeyPath:relationshipMapping.destinationKeyPath usingMapping:relationshipMapping]; + [self.delegate mappingOperation:self didNotSetUnchangedValue:destinationObject forKeyPath:destinationKeyPath usingMapping:relationshipMapping]; } } @@ -624,20 +880,21 @@ - (BOOL)mapOneToOneRelationshipWithValue:(id)value mapping:(RKRelationshipMappin - (BOOL)mapCoreDataToManyRelationshipValue:(id)valueForRelationship withMapping:(RKRelationshipMapping *)relationshipMapping { - if (! RKIsManagedObject(self.destinationObject)) return NO; + id destinationObject = self.destinationObject; + if (! RKIsManagedObject(destinationObject)) return NO; RKLogTrace(@"Mapping a to-many relationship for an `NSManagedObject`. About to apply value via mutable[Set|Array]ValueForKey"); if ([valueForRelationship isKindOfClass:[NSSet class]]) { RKLogTrace(@"Mapped `NSSet` relationship object from keyPath '%@' to '%@'. Value: %@", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath, valueForRelationship); - NSMutableSet *destinationSet = [self.destinationObject mutableSetValueForKeyPath:relationshipMapping.destinationKeyPath]; + NSMutableSet *destinationSet = [destinationObject mutableSetValueForKeyPath:relationshipMapping.destinationKeyPath]; [destinationSet setSet:valueForRelationship]; } else if ([valueForRelationship isKindOfClass:[NSArray class]]) { RKLogTrace(@"Mapped `NSArray` relationship object from keyPath '%@' to '%@'. Value: %@", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath, valueForRelationship); - NSMutableArray *destinationArray = [self.destinationObject mutableArrayValueForKeyPath:relationshipMapping.destinationKeyPath]; + NSMutableArray *destinationArray = [destinationObject mutableArrayValueForKeyPath:relationshipMapping.destinationKeyPath]; [destinationArray setArray:valueForRelationship]; } else if ([valueForRelationship isKindOfClass:[NSOrderedSet class]]) { RKLogTrace(@"Mapped `NSOrderedSet` relationship object from keyPath '%@' to '%@'. Value: %@", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath, valueForRelationship); - [self.destinationObject setValue:valueForRelationship forKeyPath:relationshipMapping.destinationKeyPath]; + [destinationObject setValue:valueForRelationship forKeyPath:relationshipMapping.destinationKeyPath]; } return YES; @@ -645,22 +902,27 @@ - (BOOL)mapCoreDataToManyRelationshipValue:(id)valueForRelationship withMapping: - (BOOL)mapOneToManyRelationshipWithValue:(id)value mapping:(RKRelationshipMapping *)relationshipMapping { + NSString *destinationKeyPath = relationshipMapping.destinationKeyPath; + // One to many relationship - RKLogDebug(@"Mapping one to many relationship value at keyPath '%@' to '%@'", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath); + RKLogDebug(@"Mapping one to many relationship value at keyPath '%@' to '%@'", relationshipMapping.sourceKeyPath, destinationKeyPath); NSMutableArray *relationshipCollection = [NSMutableArray arrayWithCapacity:[value count]]; if (RKObjectIsCollectionOfCollections(value)) { RKLogWarning(@"WARNING: Detected a relationship mapping for a collection containing another collection. This is probably not what you want. Consider using a KVC collection operator (such as @unionOfArrays) to flatten your mappable collection."); - RKLogWarning(@"Key path '%@' yielded collection containing another collection rather than a collection of objects: %@", relationshipMapping.sourceKeyPath, value); + RKLogWarning(@"Key path '%@' yielded collection containing another collection rather than a collection of objects", relationshipMapping.sourceKeyPath); + RKLogDebug(@"(Value at key path '%@': %@)", relationshipMapping.sourceKeyPath, value); } if (relationshipMapping.assignmentPolicy == RKUnionAssignmentPolicy) { RKLogDebug(@"Mapping relationship with union assignment policy: constructing combined relationship value."); - id existingObjects = [self.destinationObject valueForKeyPath:relationshipMapping.destinationKeyPath] ?: @[]; - NSArray *existingObjectsArray = nil; - NSError *error = nil; - [[RKValueTransformer defaultValueTransformer] transformValue:existingObjects toValue:&existingObjectsArray ofClass:[NSArray class] error:&error]; - [relationshipCollection addObjectsFromArray:existingObjectsArray]; + id existingObjects = [self.destinationObject valueForKeyPath:destinationKeyPath]; + if (existingObjects) { + NSArray *existingObjectsArray = nil; + NSError *error = nil; + [[RKValueTransformer defaultValueTransformer] transformValue:existingObjects toValue:&existingObjectsArray ofClass:[NSArray class] error:&error]; + [relationshipCollection addObjectsFromArray:existingObjectsArray]; + } } else if (relationshipMapping.assignmentPolicy == RKReplaceAssignmentPolicy) { if (! [self applyReplaceAssignmentPolicyForRelationshipMapping:relationshipMapping]) { @@ -668,15 +930,19 @@ - (BOOL)mapOneToManyRelationshipWithValue:(id)value mapping:(RKRelationshipMappi } } + RKMapping *relationshipDestinationMapping = relationshipMapping.mapping; + id parentSourceObject = [self parentObjectForRelationshipMapping:relationshipMapping]; + RKMappingIndexMetadata *indexMetadata = [RKMappingIndexMetadata new]; + NSArray *subOperationMetadata = RKInsertInMetadataList(self.metadataList, indexMetadata, nil); [value enumerateObjectsUsingBlock:^(id nestedObject, NSUInteger collectionIndex, BOOL *stop) { - id parentSourceObject = [self parentObjectForRelationshipMapping:relationshipMapping]; - id mappableObject = [self destinationObjectForMappingRepresentation:nestedObject parentRepresentation:parentSourceObject withMapping:relationshipMapping.mapping inRelationship:relationshipMapping]; + id mappableObject = [self destinationObjectForMappingRepresentation:nestedObject parentRepresentation:parentSourceObject withMapping:relationshipDestinationMapping inRelationship:relationshipMapping]; if (mappableObject) { - if ([self mapNestedObject:nestedObject toObject:mappableObject withRelationshipMapping:relationshipMapping metadata:@{ @"mapping": @{ @"collectionIndex": @(collectionIndex) } }]) { + indexMetadata.collectionIndex = collectionIndex; + if ([self mapNestedObject:nestedObject toObject:mappableObject parent:parentSourceObject withRelationshipMapping:relationshipMapping metadataList:subOperationMetadata]) { [relationshipCollection addObject:mappableObject]; } } else { - RKLogDebug(@"Mapping %@ declined mapping for representation %@: returned `nil` destination object.", relationshipMapping.mapping, nestedObject); + RKLogDebug(@"Mapping %@ declined mapping for representation %@: returned `nil` destination object.", relationshipDestinationMapping, nestedObject); } }]; @@ -685,14 +951,14 @@ - (BOOL)mapOneToManyRelationshipWithValue:(id)value mapping:(RKRelationshipMappi if (! [self transformValue:relationshipCollection toValue:&valueForRelationship withPropertyMapping:relationshipMapping error:&error]) return NO; // If the relationship has changed, set it - if ([self shouldSetValue:&valueForRelationship forKeyPath:relationshipMapping.destinationKeyPath usingMapping:relationshipMapping]) { + if ([self shouldSetValue:&valueForRelationship forKeyPath:destinationKeyPath usingMapping:relationshipMapping]) { if (! [self mapCoreDataToManyRelationshipValue:valueForRelationship withMapping:relationshipMapping]) { - RKLogTrace(@"Mapped relationship object from keyPath '%@' to '%@'. Value: %@", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath, valueForRelationship); - [self.destinationObject setValue:valueForRelationship forKeyPath:relationshipMapping.destinationKeyPath]; + RKLogTrace(@"Mapped relationship object from keyPath '%@' to '%@'. Value: %@", relationshipMapping.sourceKeyPath, destinationKeyPath, valueForRelationship); + [self.destinationObject setValue:valueForRelationship forKeyPath:destinationKeyPath]; } } else { if ([self.delegate respondsToSelector:@selector(mappingOperation:didNotSetUnchangedValue:forKeyPath:usingMapping:)]) { - [self.delegate mappingOperation:self didNotSetUnchangedValue:valueForRelationship forKeyPath:relationshipMapping.destinationKeyPath usingMapping:relationshipMapping]; + [self.delegate mappingOperation:self didNotSetUnchangedValue:valueForRelationship forKeyPath:destinationKeyPath usingMapping:relationshipMapping]; } return NO; @@ -704,65 +970,73 @@ - (BOOL)mapOneToManyRelationshipWithValue:(id)value mapping:(RKRelationshipMappi - (BOOL)applyRelationshipMappings { NSAssert(self.dataSource, @"Cannot perform relationship mapping without a data source"); - NSMutableArray *mappingsApplied = [NSMutableArray array]; + NSUInteger mappingsApplied = 0; + RKObjectMapping *parentObjectMapping = self.objectMapping; + id sourceObject = self.sourceObject; + id destinationObject = self.destinationObject; + id delegate = self.delegate; for (RKRelationshipMapping *relationshipMapping in [self relationshipMappings]) { if ([self isCancelled]) return NO; + NSString *sourceKeyPath = relationshipMapping.sourceKeyPath; + NSString *destinationKeyPath = relationshipMapping.destinationKeyPath; id value = nil; - if (relationshipMapping.sourceKeyPath) { - value = [self.sourceObject valueForKeyPath:relationshipMapping.sourceKeyPath]; + + if (sourceKeyPath) { + value = [sourceObject valueForKeyPath:sourceKeyPath]; } else { // The nil source keyPath indicates that we want to map directly from the parent representation - value = self.sourceObject; + value = sourceObject; + RKMapping *destinationMapping = relationshipMapping.mapping; RKObjectMapping *objectMapping = nil; - if ([relationshipMapping.mapping isKindOfClass:[RKObjectMapping class]]) { - objectMapping = (RKObjectMapping *)relationshipMapping.mapping; - } else if ([relationshipMapping.mapping isKindOfClass:[RKDynamicMapping class]]) { - objectMapping = [(RKDynamicMapping *)relationshipMapping.mapping objectMappingForRepresentation:value]; + if ([destinationMapping isKindOfClass:[RKObjectMapping class]]) { + objectMapping = (RKObjectMapping *)destinationMapping; + } else if ([destinationMapping isKindOfClass:[RKDynamicMapping class]]) { + objectMapping = [(RKDynamicMapping *)destinationMapping objectMappingForRepresentation:value]; } if (! objectMapping) continue; // Mapping declined - NSArray *propertyKeyPaths = [relationshipMapping valueForKeyPath:@"mapping.propertyMappings.sourceKeyPath"]; - if (! RKObjectContainsValueForKeyPaths(value, propertyKeyPaths)) { + if (! RKObjectContainsValueForMappings(value, objectMapping.propertyMappings)) { continue; } } // Track that we applied this mapping - [mappingsApplied addObject:relationshipMapping]; + mappingsApplied++; if (value == nil) { - RKLogDebug(@"Did not find mappable relationship value keyPath '%@'", relationshipMapping.sourceKeyPath); - if (! self.objectMapping.assignsNilForMissingRelationships) continue; + RKLogDebug(@"Did not find mappable relationship value keyPath '%@'", sourceKeyPath); + if (! parentObjectMapping.assignsNilForMissingRelationships) continue; } if (value == [NSNull null]) { - RKLogDebug(@"Found null value at keyPath '%@'", relationshipMapping.sourceKeyPath); + RKLogDebug(@"Found null value at keyPath '%@'", sourceKeyPath); value = nil; } // nil out the property if necessary if (value == nil) { - Class relationshipClass = [self.objectMapping classForKeyPath:relationshipMapping.destinationKeyPath]; + Class relationshipClass = [parentObjectMapping classForKeyPath:destinationKeyPath]; BOOL mappingToCollection = RKClassIsCollection(relationshipClass); - if (relationshipMapping.assignmentPolicy == RKUnionAssignmentPolicy && mappingToCollection) { + RKAssignmentPolicy assignmentPolicy = relationshipMapping.assignmentPolicy; + if (assignmentPolicy == RKUnionAssignmentPolicy && mappingToCollection) { // Unioning `nil` with the existing value is functionally equivalent to doing nothing, so just continue continue; - } else if (relationshipMapping.assignmentPolicy == RKUnionAssignmentPolicy) { + } else if (assignmentPolicy == RKUnionAssignmentPolicy) { NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Invalid assignment policy: cannot union a one-to-one relationship." }; self.error = [NSError errorWithDomain:RKErrorDomain code:RKMappingErrorInvalidAssignmentPolicy userInfo:userInfo]; continue; - } else if (relationshipMapping.assignmentPolicy == RKReplaceAssignmentPolicy) { + } else if (assignmentPolicy == RKReplaceAssignmentPolicy) { if (! [self applyReplaceAssignmentPolicyForRelationshipMapping:relationshipMapping]) { continue; } } - if ([self shouldSetValue:&value forKeyPath:relationshipMapping.destinationKeyPath usingMapping:relationshipMapping]) { - RKLogTrace(@"Setting nil for relationship value at keyPath '%@'", relationshipMapping.sourceKeyPath); - [self.destinationObject setValue:value forKeyPath:relationshipMapping.destinationKeyPath]; + if ([self shouldSetValue:&value forKeyPath:destinationKeyPath usingMapping:relationshipMapping]) { + RKLogTrace(@"Setting nil for relationship value at keyPath '%@'", sourceKeyPath); + [destinationObject setValue:value forKeyPath:destinationKeyPath]; } continue; @@ -785,24 +1059,27 @@ - (BOOL)applyRelationshipMappings } // Handle case where incoming content is a single object, but we want a collection - Class relationshipClass = [self.objectMapping classForKeyPath:relationshipMapping.destinationKeyPath]; + Class relationshipClass = [parentObjectMapping classForKeyPath:destinationKeyPath]; BOOL mappingToCollection = RKClassIsCollection(relationshipClass); - if (mappingToCollection && !RKObjectIsCollection(value)) { - Class orderedSetClass = NSClassFromString(@"NSOrderedSet"); + BOOL objectIsCollection = RKObjectIsCollection(value); + if (mappingToCollection && !objectIsCollection) { RKLogDebug(@"Asked to map a single object into a collection relationship. Transforming to an instance of: %@", NSStringFromClass(relationshipClass)); if ([relationshipClass isSubclassOfClass:[NSArray class]]) { value = [relationshipClass arrayWithObject:value]; + objectIsCollection = YES; } else if ([relationshipClass isSubclassOfClass:[NSSet class]]) { value = [relationshipClass setWithObject:value]; - } else if (orderedSetClass && [relationshipClass isSubclassOfClass:orderedSetClass]) { + objectIsCollection = YES; + } else if ([relationshipClass isSubclassOfClass:[NSOrderedSet class]]) { value = [relationshipClass orderedSetWithObject:value]; + objectIsCollection = YES; } else { RKLogWarning(@"Failed to transform single object"); } } BOOL setValueForRelationship; - if (RKObjectIsCollection(value)) { + if (objectIsCollection) { setValueForRelationship = [self mapOneToManyRelationshipWithValue:value mapping:relationshipMapping]; } else { setValueForRelationship = [self mapOneToOneRelationshipWithValue:value mapping:relationshipMapping]; @@ -811,27 +1088,29 @@ - (BOOL)applyRelationshipMappings if (! setValueForRelationship) continue; // Notify the delegate - if ([self.delegate respondsToSelector:@selector(mappingOperation:didSetValue:forKeyPath:usingMapping:)]) { - id setValue = [self.destinationObject valueForKeyPath:relationshipMapping.destinationKeyPath]; - [self.delegate mappingOperation:self didSetValue:setValue forKeyPath:relationshipMapping.destinationKeyPath usingMapping:relationshipMapping]; + if ([delegate respondsToSelector:@selector(mappingOperation:didSetValue:forKeyPath:usingMapping:)]) { + id setValue = [destinationObject valueForKeyPath:destinationKeyPath]; + [delegate mappingOperation:self didSetValue:setValue forKeyPath:destinationKeyPath usingMapping:relationshipMapping]; } // Fail out if a validation error has occurred if (self.error) break; } - return [mappingsApplied count] > 0; + return mappingsApplied > 0; } - (void)applyNestedMappings { - RKAttributeMapping *attributeMapping = [self.objectMapping mappingForSourceKeyPath:RKObjectMappingNestingAttributeKeyName]; + RKObjectMapping *objectMapping = self.objectMapping; + RKAttributeMapping *attributeMapping = [objectMapping mappingForSourceKeyPath:RKObjectMappingNestingAttributeKeyName]; if (attributeMapping) { RKLogDebug(@"Found nested mapping definition to attribute '%@'", attributeMapping.destinationKeyPath); id attributeValue = [[self.sourceObject allKeys] lastObject]; if (attributeValue) { RKLogDebug(@"Found nesting value of '%@' for attribute '%@'", attributeValue, attributeMapping.destinationKeyPath); - self.nestedAttributeSubstitution = @{ attributeMapping.destinationKeyPath: attributeValue }; + self.nestedAttributeSubstitutionKey = attributeMapping.destinationKeyPath; + self.nestedAttributeSubstitutionValue = attributeValue; [self applyAttributeMapping:attributeMapping withValue:attributeValue]; } else { RKLogWarning(@"Unable to find nesting value for attribute '%@'", attributeMapping.destinationKeyPath); @@ -839,13 +1118,14 @@ - (void)applyNestedMappings } // Serialization - attributeMapping = [self.objectMapping mappingForDestinationKeyPath:RKObjectMappingNestingAttributeKeyName]; + attributeMapping = [objectMapping mappingForDestinationKeyPath:RKObjectMappingNestingAttributeKeyName]; if (attributeMapping) { RKLogDebug(@"Found nested mapping definition to attribute '%@'", attributeMapping.destinationKeyPath); id attributeValue = [self.sourceObject valueForKeyPath:attributeMapping.sourceKeyPath]; if (attributeValue) { RKLogDebug(@"Found nesting value of '%@' for attribute '%@'", attributeValue, attributeMapping.sourceKeyPath); - self.nestedAttributeSubstitution = @{ attributeMapping.sourceKeyPath: attributeValue }; + self.nestedAttributeSubstitutionKey = attributeMapping.sourceKeyPath; + self.nestedAttributeSubstitutionValue = attributeValue; } else { RKLogWarning(@"Unable to find nesting value for attribute '%@'", attributeMapping.destinationKeyPath); } @@ -854,50 +1134,73 @@ - (void)applyNestedMappings - (void)cancel { - [super cancel]; + self.cancelled = YES; RKLogDebug(@"Mapping operation cancelled: %@", self); } +- (void)start +{ + [self main]; +} + - (void)main { if ([self isCancelled]) return; // Handle metadata - self.sourceObject = [[RKMappingSourceObject alloc] initWithObject:self.sourceObject parentObject:self.parentSourceObject rootObject:self.rootSourceObject metadata:self.metadata]; + id parentSourceObject = self.parentSourceObject; + id sourceObject = [[RKMappingSourceObject alloc] initWithObject:self.sourceObject parentObject:parentSourceObject rootObject:self.rootSourceObject metadata:self.metadataList]; + self.sourceObject = sourceObject; RKLogDebug(@"Starting mapping operation..."); RKLogTrace(@"Performing mapping operation: %@", self); + id dataSource = self.dataSource; + id delegate = self.delegate; + RKMapping *mapping = self.mapping; + RKObjectMapping *objectMapping; + if (! self.destinationObject) { - self.destinationObject = [self destinationObjectForMappingRepresentation:self.sourceObject parentRepresentation:self.parentSourceObject withMapping:self.mapping inRelationship:nil]; + self.destinationObject = [self destinationObjectForMappingRepresentation:sourceObject parentRepresentation:parentSourceObject withMapping:mapping inRelationship:nil]; if (! self.destinationObject) { RKLogDebug(@"Mapping operation failed: Given nil destination object and unable to instantiate a destination object for mapping."); NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Cannot perform a mapping operation with a nil destination object." }; self.error = [NSError errorWithDomain:RKErrorDomain code:RKMappingErrorNilDestinationObject userInfo:userInfo]; return; } + self.newDestinationObject = YES; } + self.collectsMappingInfo = (![dataSource respondsToSelector:@selector(mappingOperationShouldCollectMappingInfo:)] || + [dataSource mappingOperationShouldCollectMappingInfo:self]); + + self.shouldSetUnchangedValues = ([self.dataSource respondsToSelector:@selector(mappingOperationShouldSetUnchangedValues:)] && + [self.dataSource mappingOperationShouldSetUnchangedValues:self]); + // Determine the concrete mapping if we were initialized with a dynamic mapping - if ([self.mapping isKindOfClass:[RKDynamicMapping class]]) { - self.objectMapping = [(RKDynamicMapping *)self.mapping objectMappingForRepresentation:self.sourceObject]; - if (! self.objectMapping) { + if ([mapping isKindOfClass:[RKDynamicMapping class]]) { + self.objectMapping = objectMapping = [(RKDynamicMapping *)mapping objectMappingForRepresentation:sourceObject]; + if (! objectMapping) { NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"A dynamic mapping failed to return a concrete object mapping matching the representation being mapped." }; self.error = [NSError errorWithDomain:RKErrorDomain code:RKMappingErrorUnableToDetermineMapping userInfo:userInfo]; return; } - RKLogDebug(@"RKObjectMappingOperation was initialized with a dynamic mapping. Determined concrete mapping = %@", self.objectMapping); + RKLogDebug(@"RKObjectMappingOperation was initialized with a dynamic mapping. Determined concrete mapping = %@", objectMapping); - if ([self.delegate respondsToSelector:@selector(mappingOperation:didSelectObjectMapping:forDynamicMapping:)]) { - [self.delegate mappingOperation:self didSelectObjectMapping:self.objectMapping forDynamicMapping:(RKDynamicMapping *)self.mapping]; + if ([delegate respondsToSelector:@selector(mappingOperation:didSelectObjectMapping:forDynamicMapping:)]) { + [delegate mappingOperation:self didSelectObjectMapping:objectMapping forDynamicMapping:(RKDynamicMapping *)mapping]; + } + if (self.collectsMappingInfo) { + self.mappingInfo = [[RKMappingInfo alloc] initWithObjectMapping:objectMapping dynamicMapping:(RKDynamicMapping *)mapping]; + } + } else if ([mapping isKindOfClass:[RKObjectMapping class]]) { + self.objectMapping = objectMapping = (RKObjectMapping *)mapping; + if (self.collectsMappingInfo) { + self.mappingInfo = [[RKMappingInfo alloc] initWithObjectMapping:objectMapping dynamicMapping:nil]; } - self.mappingInfo = [[RKMappingInfo alloc] initWithObjectMapping:self.objectMapping dynamicMapping:(RKDynamicMapping *)self.mapping]; - } else if ([self.mapping isKindOfClass:[RKObjectMapping class]]) { - self.objectMapping = (RKObjectMapping *)self.mapping; - self.mappingInfo = [[RKMappingInfo alloc] initWithObjectMapping:self.objectMapping dynamicMapping:nil]; } - BOOL canSkipMapping = [self.dataSource respondsToSelector:@selector(mappingOperationShouldSkipPropertyMapping:)] && [self.dataSource mappingOperationShouldSkipPropertyMapping:self]; + BOOL canSkipMapping = [dataSource respondsToSelector:@selector(mappingOperationShouldSkipPropertyMapping:)] && [dataSource mappingOperationShouldSkipPropertyMapping:self]; if (! canSkipMapping) { [self applyNestedMappings]; if ([self isCancelled]) return; @@ -917,9 +1220,9 @@ - (void)main // We did some mapping work, if there's no error let's commit our changes to the data source if (self.error == nil) { - if ([self.dataSource respondsToSelector:@selector(commitChangesForMappingOperation:error:)]) { + if ([dataSource respondsToSelector:@selector(commitChangesForMappingOperation:error:)]) { NSError *error = nil; - BOOL success = [self.dataSource commitChangesForMappingOperation:self error:&error]; + BOOL success = [dataSource commitChangesForMappingOperation:self error:&error]; if (! success) { self.error = error; } @@ -928,8 +1231,8 @@ - (void)main } if (self.error) { - if ([self.delegate respondsToSelector:@selector(mappingOperation:didFailWithError:)]) { - [self.delegate mappingOperation:self didFailWithError:self.error]; + if ([delegate respondsToSelector:@selector(mappingOperation:didFailWithError:)]) { + [delegate mappingOperation:self didFailWithError:self.error]; } RKLogDebug(@"Failed mapping operation: %@", [self.error localizedDescription]); diff --git a/Code/ObjectMapping/RKMappingOperationDataSource.h b/Code/ObjectMapping/RKMappingOperationDataSource.h index 83eeb8abff..2d06b34163 100644 --- a/Code/ObjectMapping/RKMappingOperationDataSource.h +++ b/Code/ObjectMapping/RKMappingOperationDataSource.h @@ -47,6 +47,23 @@ @optional +/** + Asks the data source for the target object for an object mapping operation the mapping object that will be used to perform the mapping. + + If not implemented or it returns nil, then the + `mappingOperation:targetObjectForRepresentation:withMapping:inRelationship:` method will be called to determine the target. + + It is preferable to implement this method if the `representation` is not needed to determine the target object, + as obtaining that value is somewhat expensive. + + @param mappingOperation The mapping operation requesting the target object. + @param representation A dictionary representation of the properties to be mapped onto the retrieved target object. + @param mapping The object mapping to be used to perform a mapping from the representation to the target object. + @return A key-value coding compliant object to perform the mapping on to. + */ +- (id)mappingOperation:(RKMappingOperation *)mappingOperation targetObjectForMapping:(RKObjectMapping *)mapping inRelationship:(RKRelationshipMapping *)relationshipMapping; + + /** Tells the data source to commit any changes to the underlying data store. @@ -78,4 +95,16 @@ - (BOOL)mappingOperationShouldSkipPropertyMapping:(RKMappingOperation *)mappingOperation; +/** + Asks the data source if the mapping operation should collect `RKMappingInfo` information during the mapping + (stored in the `mappingInfo` property). If not needed, it can be a substantially faster to skip it. The + `mappingInfo` property will be nil if not collected. + + If this method is not implemented by the data source, then the mapping operation defaults to `YES`. + + @param mappingOperation The mapping operation that is querying the data source. + @return `YES` if the mapping operation should collect mapping information, else `NO`. +*/ +- (BOOL)mappingOperationShouldCollectMappingInfo:(RKMappingOperation *)mappingOperation; + @end diff --git a/Code/ObjectMapping/RKObjectMapping.h b/Code/ObjectMapping/RKObjectMapping.h index 5abba00ef8..43b2c93d09 100644 --- a/Code/ObjectMapping/RKObjectMapping.h +++ b/Code/ObjectMapping/RKObjectMapping.h @@ -105,7 +105,7 @@ /** The aggregate collection of attribute and relationship mappings within this object mapping. */ -@property (nonatomic, strong, readonly) NSArray *propertyMappings; +@property (nonatomic, copy, readonly) NSArray *propertyMappings; /** Returns the property mappings of the receiver in a dictionary, where the keys are the source key paths and the values are instances of `RKAttributeMapping` or `RKRelationshipMapping`. @@ -128,6 +128,20 @@ */ @property (nonatomic, readonly) NSArray *attributeMappings; +/** + The collection of single key attribute mappings within this object mapping. + + These are mappings where the source key path is a single key, and not a key path with multiple components. + */ +@property (nonatomic, readonly) NSArray *keyAttributeMappings; + +/** + The collection of key path attribute mappings within this object mapping. + + A key path mapping is one where the source key path is actually a path with multiple components. + */ +@property (nonatomic, readonly) NSArray *keyPathAttributeMappings; + /** The collection of relationship mappings within this object mapping. */ diff --git a/Code/ObjectMapping/RKObjectMapping.m b/Code/ObjectMapping/RKObjectMapping.m index 07342b9ad5..a6d02577af 100644 --- a/Code/ObjectMapping/RKObjectMapping.m +++ b/Code/ObjectMapping/RKObjectMapping.m @@ -104,7 +104,14 @@ @interface RKPropertyMapping () @interface RKObjectMapping () @property (nonatomic, weak, readwrite) Class objectClass; -@property (nonatomic, strong) NSMutableArray *mutablePropertyMappings; +@property (nonatomic, copy, readwrite) NSArray *propertyMappings; + +@property (nonatomic, strong) NSArray *relationshipMappings; +@property (nonatomic, strong) NSArray *attributeMappings; +@property (nonatomic, strong) NSArray *keyAttributeMappings; +@property (nonatomic, strong) NSArray *keyPathAttributeMappings; +@property (nonatomic, strong) NSMutableDictionary *propertiesBySourceKeyPath; +@property (nonatomic, strong) NSMutableDictionary *propertiesByDestinationKeyPath; @property (nonatomic, weak, readonly) NSArray *mappedKeyPaths; @property (nonatomic, copy) RKSourceToDesinationKeyTransformationBlock sourceToDestinationKeyTransformationBlock; @@ -148,7 +155,13 @@ - (id)initWithClass:(Class)objectClass self = [super init]; if (self) { self.objectClass = objectClass; - self.mutablePropertyMappings = [NSMutableArray new]; + self.propertyMappings = [NSArray new]; + self.relationshipMappings = [NSArray new]; + self.attributeMappings = [NSArray new]; + self.keyAttributeMappings = [NSArray new]; + self.keyPathAttributeMappings = [NSArray new]; + self.propertiesBySourceKeyPath = [NSMutableDictionary new]; + self.propertiesByDestinationKeyPath = [NSMutableDictionary new]; self.assignsDefaultValueForMissingAttributes = NO; self.assignsNilForMissingRelationships = NO; self.forceCollectionMapping = NO; @@ -174,7 +187,6 @@ - (id)copyWithZone:(NSZone *)zone { RKObjectMapping *copy = [[[self class] allocWithZone:zone] initWithClass:self.objectClass]; [copy copyPropertiesFromMapping:self]; - copy.mutablePropertyMappings = [NSMutableArray new]; for (RKPropertyMapping *propertyMapping in self.propertyMappings) { [copy addPropertyMapping:[propertyMapping copy]]; @@ -188,31 +200,14 @@ + (void)setDefaultSourceToDestinationKeyTransformationBlock:(RKSourceToDesinatio defaultSourceToDestinationKeyTransformationBlock = block; } -- (NSArray *)propertyMappings -{ - return [NSArray arrayWithArray:_mutablePropertyMappings]; -} - - (NSDictionary *)propertyMappingsBySourceKeyPath { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:[self.propertyMappings count]]; - for (RKPropertyMapping *propertyMapping in self.propertyMappings) { - if (! propertyMapping.sourceKeyPath) continue; - [dictionary setObject:propertyMapping forKey:propertyMapping.sourceKeyPath]; - } - - return dictionary; + return [self.propertiesBySourceKeyPath copy]; } - (NSDictionary *)propertyMappingsByDestinationKeyPath { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:[self.propertyMappings count]]; - for (RKPropertyMapping *propertyMapping in self.propertyMappings) { - if (! propertyMapping.destinationKeyPath) continue; - [dictionary setObject:propertyMapping forKey:propertyMapping.destinationKeyPath]; - } - - return dictionary; + return [self.propertiesByDestinationKeyPath copy]; } - (NSArray *)mappedKeyPaths @@ -222,36 +217,64 @@ - (NSArray *)mappedKeyPaths - (NSArray *)attributeMappings { - NSMutableArray *mappings = [NSMutableArray array]; - for (RKAttributeMapping *mapping in self.propertyMappings) { - if ([mapping isMemberOfClass:[RKAttributeMapping class]]) { - [mappings addObject:mapping]; - } - } - - return mappings; + return _attributeMappings; } - (NSArray *)relationshipMappings { - NSMutableArray *mappings = [NSMutableArray array]; - for (RKAttributeMapping *mapping in self.propertyMappings) { - if ([mapping isMemberOfClass:[RKRelationshipMapping class]]) { - [mappings addObject:mapping]; - } - } + return _relationshipMappings; +} + +- (NSArray *)keyAttributeMappings +{ + return _keyAttributeMappings; +} + +- (NSArray *)keyPathAttributeMappings +{ + return _keyPathAttributeMappings; +} + +static NSArray *RKAddProperty(NSArray *array, RKPropertyMapping *mapping) +{ + return (array)? [array arrayByAddingObject:mapping] : [NSArray arrayWithObject:mapping]; +} - return mappings; +static NSArray *RKRemoveProperty(NSArray *array, RKPropertyMapping *mapping) +{ + if (![array containsObject:mapping]) return array; + NSMutableArray *mappings = [[NSMutableArray alloc] initWithArray:array]; //alloc/init avoids autorelease + [mappings removeObject:mapping]; + return [mappings copy]; } - (void)addPropertyMapping:(RKPropertyMapping *)propertyMapping { NSAssert1([[self mappedKeyPaths] containsObject:propertyMapping.destinationKeyPath] == NO, @"Unable to add mapping for keyPath %@, one already exists...", propertyMapping.destinationKeyPath); - NSAssert(self.mutablePropertyMappings, @"self.mutablePropertyMappings is nil"); + NSAssert(self.propertyMappings, @"self.propertyMappings is nil"); NSAssert(propertyMapping.objectMapping == nil, @"Cannot add a property mapping object that has already been added to another `RKObjectMapping` object. You probably want to obtain a copy of the mapping: `[propertyMapping copy]`"); propertyMapping.objectMapping = self; - [self.mutablePropertyMappings addObject:propertyMapping]; + self.propertyMappings = [self.propertyMappings arrayByAddingObject:propertyMapping]; + if (propertyMapping.sourceKeyPath) [self.propertiesBySourceKeyPath setObject:propertyMapping forKey:propertyMapping.sourceKeyPath]; + if (propertyMapping.destinationKeyPath) [self.propertiesByDestinationKeyPath setObject:propertyMapping forKey:propertyMapping.destinationKeyPath]; + if ([propertyMapping isMemberOfClass:[RKRelationshipMapping class]]) { + self.relationshipMappings = RKAddProperty(self.relationshipMappings, propertyMapping); + } + else if ([propertyMapping isMemberOfClass:[RKAttributeMapping class]]) + { + self.attributeMappings = RKAddProperty(self.attributeMappings, propertyMapping); + if ([propertyMapping.sourceKeyPath rangeOfString:@"." options:NSLiteralSearch].length == 0) { + self.keyAttributeMappings = RKAddProperty(self.keyAttributeMappings, propertyMapping); + } + else { + self.keyPathAttributeMappings = RKAddProperty(self.keyPathAttributeMappings, propertyMapping); + } + } + + if (propertyMapping.propertyValueClass == Nil && ![self.objectClass isSubclassOfClass:[NSDictionary class]]) { + propertyMapping.propertyValueClass = [self classForKeyPath:propertyMapping.destinationKeyPath]; + } } - (void)addPropertyMappingsFromArray:(NSArray *)arrayOfPropertyMappings @@ -270,31 +293,24 @@ - (NSString *)description - (id)mappingForSourceKeyPath:(NSString *)sourceKeyPath { - for (RKPropertyMapping *mapping in self.propertyMappings) { - if (mapping.sourceKeyPath == sourceKeyPath || [mapping.sourceKeyPath isEqualToString:sourceKeyPath]) { - return mapping; - } - } - - return nil; + return [_propertiesBySourceKeyPath objectForKey:sourceKeyPath]; } - (id)mappingForDestinationKeyPath:(NSString *)destinationKeyPath { - for (RKPropertyMapping *mapping in self.propertyMappings) { - if ([mapping.destinationKeyPath isEqualToString:destinationKeyPath]) { - return mapping; - } - } - - return nil; + return [_propertiesByDestinationKeyPath objectForKey:destinationKeyPath]; } // Evaluate each component individually so that camelization, etc. considers each component individually - (NSString *)transformSourceKeyPath:(NSString *)keyPath { if (!self.sourceToDestinationKeyTransformationBlock) return keyPath; - + + NSRange dotRange = [keyPath rangeOfString:@"." options:NSLiteralSearch]; + if (dotRange.length == 0) { + return self.sourceToDestinationKeyTransformationBlock(self, keyPath); + } + NSArray *components = [keyPath componentsSeparatedByString:@"."]; NSMutableArray *mutableComponents = [NSMutableArray arrayWithCapacity:[components count]]; [components enumerateObjectsUsingBlock:^(id component, NSUInteger idx, BOOL *stop) { @@ -341,9 +357,15 @@ - (void)addRelationshipMappingWithSourceKeyPath:(NSString *)sourceKeyPath mappin - (void)removePropertyMapping:(RKPropertyMapping *)attributeOrRelationshipMapping { - if ([self.mutablePropertyMappings containsObject:attributeOrRelationshipMapping]) { + if ([self.propertyMappings containsObject:attributeOrRelationshipMapping]) { attributeOrRelationshipMapping.objectMapping = nil; - [self.mutablePropertyMappings removeObject:attributeOrRelationshipMapping]; + self.propertyMappings = RKRemoveProperty(self.propertyMappings, attributeOrRelationshipMapping); + self.relationshipMappings = RKRemoveProperty(self.relationshipMappings, attributeOrRelationshipMapping); + self.attributeMappings = RKRemoveProperty(self.attributeMappings, attributeOrRelationshipMapping); + self.keyAttributeMappings = RKRemoveProperty(self.keyAttributeMappings, attributeOrRelationshipMapping); + self.keyPathAttributeMappings = RKRemoveProperty(self.keyPathAttributeMappings, attributeOrRelationshipMapping); + [self.propertiesBySourceKeyPath removeObjectForKey:attributeOrRelationshipMapping.sourceKeyPath]; + [self.propertiesByDestinationKeyPath removeObjectForKey:attributeOrRelationshipMapping.destinationKeyPath]; } } @@ -402,10 +424,18 @@ - (Class)classForProperty:(NSString *)propertyName - (Class)classForKeyPath:(NSString *)keyPath { + if (keyPath == nil) return self.objectClass; + + RKPropertyInspector *inspector = [RKPropertyInspector sharedInspector]; + + if ([keyPath rangeOfString:@"." options:NSLiteralSearch].length == 0) { + return [inspector classForPropertyNamed:keyPath ofClass:self.objectClass isPrimitive:nil]; + } + NSArray *components = [keyPath componentsSeparatedByString:@"."]; Class propertyClass = self.objectClass; for (NSString *property in components) { - propertyClass = [[RKPropertyInspector sharedInspector] classForPropertyNamed:property ofClass:propertyClass isPrimitive:nil]; + propertyClass = [inspector classForPropertyNamed:property ofClass:propertyClass isPrimitive:nil]; if (! propertyClass) break; } diff --git a/Code/ObjectMapping/RKObjectMappingMatcher.h b/Code/ObjectMapping/RKObjectMappingMatcher.h index 650e905da6..577550cd98 100644 --- a/Code/ObjectMapping/RKObjectMappingMatcher.h +++ b/Code/ObjectMapping/RKObjectMappingMatcher.h @@ -48,6 +48,16 @@ */ + (instancetype)matcherWithKeyPath:(NSString *)keyPath expectedClass:(Class)expectedClass objectMapping:(RKObjectMapping *)objectMapping; +/** + Creates and returns a key path matcher object with a given key path, and a map of expected values to associated RKObjectMapping objects that applies in the event of a positive match with its associated value. This method can evaluate the keyPath once + + @param keyPath The key path to obtain the comparison value from the object being matched via `valueForKeyPath:`. + @param expectedValue The value that is expected to be read from `keyPath` if there is a match. + @param objectMapping The object mapping object that applies if the comparison value is equal to the expected value. + @return The receiver, initialized with the given key path and expected value map. + */ ++ (instancetype)matcherWithKeyPath:(NSString *)keyPath expectedValueMap:(NSDictionary *)valueToObjectMapping; + ///-------------------------------------- /// @name Constructing Predicate Matchers ///-------------------------------------- @@ -61,10 +71,25 @@ */ + (instancetype)matcherWithPredicate:(NSPredicate *)predicate objectMapping:(RKObjectMapping *)objectMapping; + +/** + Creates and returns a matcher object with a given block which returns the RKObjectMapping instance to use, and an optional array of possible object mappings which could be returned. + + @param possibleMappings The list of known possible RKObjectMapping instances which could be returned. This is used to aid RKDynamicMapping's -objectMappings method which is used in some instances, but is not required for mapping. The block could return a new instance if needed. + @param block The block with which to evaluate the matched object, and return the object mapping to use. Return nil if no match (i.e. a `NO` return from the `-matches:` method). + @return The receiver, initialized with the given block ans possible mappings. + */ ++ (instancetype)matcherWithPossibleMappings:(NSArray *)mappings block:(RKObjectMapping *(^)(id representation))block; + ///----------------------------------- /// @name Accessing the Object Mapping ///----------------------------------- +/** + Returns the list of all known RKObjectMapping instances which could be returned from this matcher. This is called when added to or removed from an RKDynamicMapping, and is used to populate the `objectMappings` property there. The default implementation returns the single value set in the `objectMapping` property, so if that is the only possibility then this method does not need to be overridden. + */ +@property (nonatomic, readonly) NSArray *possibleObjectMappings; + /** The object mapping object that applies when the receiver matches a given object. diff --git a/Code/ObjectMapping/RKObjectMappingMatcher.m b/Code/ObjectMapping/RKObjectMappingMatcher.m index 6ecce3e73d..7ec3999c9d 100644 --- a/Code/ObjectMapping/RKObjectMappingMatcher.m +++ b/Code/ObjectMapping/RKObjectMappingMatcher.m @@ -31,12 +31,26 @@ - (id)initWithKeyPath:(NSString *)keyPath expectedClass:(Class)expectedClass obj @end +@interface RKKeyPathValueMapObjectMappingMatcher : RKObjectMappingMatcher +@property (nonatomic, copy) NSString *keyPath; +@property (nonatomic, copy) NSDictionary *valueMap; + +- (instancetype)initWithKeyPath:(NSString *)keyPath expectedValueMap:(NSDictionary *)valueToObjectMapping; +@end + @interface RKPredicateObjectMappingMatcher : RKObjectMappingMatcher @property (nonatomic, strong) NSPredicate *predicate; - (id)initWithPredicate:(NSPredicate *)predicate objectMapping:(RKObjectMapping *)objectMapping; @end +@interface RKBlockObjectMatchingMatcher : RKObjectMappingMatcher +@property (nonatomic, copy) NSArray *possibleMappings; +@property (nonatomic, copy) RKObjectMapping *(^block)(id representation); +- (instancetype)initWithPossibleMappings:(NSArray *)mappings block:(RKObjectMapping *(^)(id representation))block; +@end + + /////////////////////////////////////////////////////////////////////////////////////////////////// @implementation RKObjectMappingMatcher @@ -51,11 +65,21 @@ + (instancetype)matcherWithKeyPath:(NSString *)keyPath expectedClass:(Class)expe return [[RKKeyPathClassObjectMappingMatcher alloc] initWithKeyPath:keyPath expectedClass:expectedClass objectMapping:objectMapping]; } ++ (instancetype)matcherWithKeyPath:(NSString *)keyPath expectedValueMap:(NSDictionary *)valueToObjectMapping +{ + return [[RKKeyPathValueMapObjectMappingMatcher alloc] initWithKeyPath:keyPath expectedValueMap:valueToObjectMapping]; +} + + (instancetype)matcherWithPredicate:(NSPredicate *)predicate objectMapping:(RKObjectMapping *)objectMapping { return [[RKPredicateObjectMappingMatcher alloc] initWithPredicate:predicate objectMapping:objectMapping]; } ++ (instancetype)matcherWithPossibleMappings:(NSArray *)mappings block:(RKObjectMapping *(^)(id representation))block +{ + return [[RKBlockObjectMatchingMatcher alloc] initWithPossibleMappings:mappings block:block]; +} + - (id)init { self = [super init]; @@ -71,6 +95,12 @@ - (id)init return self; } +- (NSArray *)possibleObjectMappings +{ + RKObjectMapping *mapping = self.objectMapping; + return mapping ? @[mapping] : nil; +} + - (BOOL)matches:(id)object { return NO; @@ -139,6 +169,45 @@ - (NSString *)description @end +@implementation RKKeyPathValueMapObjectMappingMatcher + +- (instancetype)initWithKeyPath:(NSString *)keyPath expectedValueMap:(NSDictionary *)valueToObjectMapping +{ + NSParameterAssert(keyPath); + NSParameterAssert(valueToObjectMapping.count > 0); + self = [super init]; + if (self) { + self.keyPath = keyPath; + self.valueMap = valueToObjectMapping; + } + + return self; +} + +- (NSArray *)possibleObjectMappings +{ + return [self.valueMap allValues]; +} + +- (BOOL)matches:(id)object +{ + id value = [object valueForKeyPath:self.keyPath]; + RKObjectMapping *mapping = [self.valueMap objectForKey:value]; + if (mapping) { + self.objectMapping = mapping; + return YES; + } + + return NO; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p when `%@` in '%@'>", NSStringFromClass([self class]), self, self.keyPath, [self.valueMap allKeys]]; +} + +@end + @implementation RKPredicateObjectMappingMatcher - (id)initWithPredicate:(NSPredicate *)predicate objectMapping:(RKObjectMapping *)objectMapping @@ -165,3 +234,40 @@ - (NSString *)description } @end + +@implementation RKBlockObjectMatchingMatcher + +- (instancetype)initWithPossibleMappings:(NSArray *)mappings block:(RKObjectMapping *(^)(id representation))block +{ + NSParameterAssert(block); + self = [super init]; + if (self) { + self.block = block; + self.possibleMappings = mappings; + } + + return self; +} + +- (NSArray *)possibleObjectMappings +{ + return self.possibleMappings; +} + +- (BOOL)matches:(id)object +{ + RKObjectMapping *mapping = self.block(object); + if (mapping) { + self.objectMapping = mapping; + return YES; + } + + return NO; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p when '%@'>", NSStringFromClass([self class]), self, self.block]; +} + +@end diff --git a/Code/ObjectMapping/RKObjectMappingOperationDataSource.m b/Code/ObjectMapping/RKObjectMappingOperationDataSource.m index 1e3cbf470d..e8aba71f7f 100644 --- a/Code/ObjectMapping/RKObjectMappingOperationDataSource.m +++ b/Code/ObjectMapping/RKObjectMappingOperationDataSource.m @@ -20,6 +20,7 @@ #import "RKObjectMappingOperationDataSource.h" #import "RKObjectMapping.h" +#import "RKMappingOperation.h" @implementation RKObjectMappingOperationDataSource @@ -29,4 +30,19 @@ - (id)mappingOperation:(RKMappingOperation *)mappingOperation targetObjectForRep return [mapping.objectClass new]; } +- (id)mappingOperation:(RKMappingOperation *)mappingOperation targetObjectForMapping:(RKObjectMapping *)mapping inRelationship:(RKRelationshipMapping *)relationshipMapping +{ + return [mapping.objectClass new]; +} + +- (BOOL)mappingOperationShouldCollectMappingInfo:(RKMappingOperation *)mappingOperation +{ + return NO; +} + +- (BOOL)mappingOperationShouldSetUnchangedValues:(RKMappingOperation *)mappingOperation +{ + return [mappingOperation isNewDestinationObject]; +} + @end diff --git a/Code/ObjectMapping/RKObjectUtilities.m b/Code/ObjectMapping/RKObjectUtilities.m index 4e1bc31b02..052335e75e 100644 --- a/Code/ObjectMapping/RKObjectUtilities.m +++ b/Code/ObjectMapping/RKObjectUtilities.m @@ -26,28 +26,7 @@ BOOL RKObjectIsEqualToObject(id object, id anotherObject) { NSCAssert(object, @"Expected object not to be nil"); NSCAssert(anotherObject, @"Expected anotherObject not to be nil"); - SEL comparisonSelector; - if ([object isKindOfClass:[NSString class]] && [anotherObject isKindOfClass:[NSString class]]) { - comparisonSelector = @selector(isEqualToString:); - } else if ([object isKindOfClass:[NSNumber class]] && [anotherObject isKindOfClass:[NSNumber class]]) { - comparisonSelector = @selector(isEqualToNumber:); - } else if ([object isKindOfClass:[NSDate class]] && [anotherObject isKindOfClass:[NSDate class]]) { - comparisonSelector = @selector(isEqualToDate:); - } else if ([object isKindOfClass:[NSArray class]] && [anotherObject isKindOfClass:[NSArray class]]) { - comparisonSelector = @selector(isEqualToArray:); - } else if ([object isKindOfClass:[NSDictionary class]] && [anotherObject isKindOfClass:[NSDictionary class]]) { - comparisonSelector = @selector(isEqualToDictionary:); - } else if ([object isKindOfClass:[NSSet class]] && [anotherObject isKindOfClass:[NSSet class]]) { - comparisonSelector = @selector(isEqualToSet:); - } else { - comparisonSelector = @selector(isEqual:); - } - - // Comparison magic using function pointers. See this page for details: http://www.red-sweater.com/blog/320/abusing-objective-c-with-class - // Original code courtesy of Greg Parker - // This is necessary because isEqualToNumber will return negative integer values that aren't coercable directly to BOOL's without help [sbw] - BOOL (*ComparisonSender)(id, SEL, id) = (BOOL (*)(id, SEL, id))objc_msgSend; - return ComparisonSender(object, comparisonSelector, anotherObject); + return (object == anotherObject) || [object isEqual:anotherObject]; } BOOL RKClassIsCollection(Class aClass) diff --git a/Code/ObjectMapping/RKPropertyInspector.h b/Code/ObjectMapping/RKPropertyInspector.h index 49b3c04199..ba28926dc6 100644 --- a/Code/ObjectMapping/RKPropertyInspector.h +++ b/Code/ObjectMapping/RKPropertyInspector.h @@ -22,26 +22,35 @@ @class NSEntityDescription; -///-------------------------------------------------- -/// @name Keys for the Property Inspection Dictionary -///-------------------------------------------------- +/** + * The object used to store attributes for each property; used as the value in the class dictionary. + */ +@interface RKPropertyInspectorPropertyInfo : NSObject + +/** + Creates a new RKPropertyInspectorPropertyInfo instance with the given information + */ ++ (id)propertyInfoWithName:(NSString *)name keyValueClass:(Class)kvClass isPrimitive:(BOOL)isPrimitive; /** The name of the property */ -extern NSString * const RKPropertyInspectionNameKey; +@property (nonatomic, copy, readonly) NSString *name; /** The class used for key-value coding access to the property. - If the property is an object object type, then the class set for this key will be the type of the property. If the property is a primitive, then the class set for the key will be the boxed type used for KVC access to the property. For example, an `NSInteger` property is boxed to an `NSNumber` for KVC purposes. + If the property is an object type, then the class set for this key will be the type of the property. If the property is a primitive, then the class set for the key will be the boxed type used for KVC access to the property. For example, an `NSInteger` property is boxed to an `NSNumber` for KVC purposes. */ -extern NSString * const RKPropertyInspectionKeyValueCodingClassKey; +@property (nonatomic, strong, readonly) Class keyValueCodingClass; /** - A Boolean value that indicates if the property is a primitive (non-object) value. + A BOOL value that indicates if the property is a primitive (non-object) value. */ -extern NSString * const RKPropertyInspectionIsPrimitiveKey; +@property (nonatomic, readonly) BOOL isPrimitive; + +@end + /** The `RKPropertyInspector` class provides an interface for introspecting the properties and attributes of classes using the reflection capabilities of the Objective-C runtime. Once inspected, the properties inspection details are cached. @@ -64,7 +73,7 @@ extern NSString * const RKPropertyInspectionIsPrimitiveKey; ///------------------------------------------------------ /** - Returns a dictionary keyed by property name that includes the key-value coding class of the property and a Boolean indicating if the property is backed by a primitive (non-object) value. The dictionary for each property includes details about the key-value coding class representing the property and if the property is backed by a primitive type. + Returns a dictionary keyed by property name that includes the key-value coding class of the property and a Boolean indicating if the property is backed by a primitive (non-object) value. The RKPropertyInspectorPropertyInfo object for each property includes details about the key-value coding class representing the property and if the property is backed by a primitive type. @param objectClass The class to inspect the properties of. @return A dictionary keyed by property name that includes details about all declared properties of the class. diff --git a/Code/ObjectMapping/RKPropertyInspector.m b/Code/ObjectMapping/RKPropertyInspector.m index f3221d0d7e..719e506017 100644 --- a/Code/ObjectMapping/RKPropertyInspector.m +++ b/Code/ObjectMapping/RKPropertyInspector.m @@ -31,6 +31,27 @@ NSString * const RKPropertyInspectionKeyValueCodingClassKey = @"keyValueCodingClass"; NSString * const RKPropertyInspectionIsPrimitiveKey = @"isPrimitive"; + +@implementation RKPropertyInspectorPropertyInfo + ++ (id)propertyInfoWithName:(NSString *)name keyValueClass:(Class)kvClass isPrimitive:(BOOL)isPrimitive +{ + return [[self alloc] initWithName:name keyValueClass:kvClass isPrimitive:isPrimitive]; +} + +- (id)initWithName:(NSString *)name keyValueClass:(Class)kvClass isPrimitive:(BOOL)isPrimitive +{ + if (self = [super init]) { + _name = [name copy]; + _keyValueCodingClass = kvClass; + _isPrimitive = isPrimitive; + } + return self; +} + +@end + + @interface RKPropertyInspector () #if OS_OBJECT_USE_OBJC @property (nonatomic, strong) dispatch_queue_t queue; @@ -110,10 +131,11 @@ - (NSDictionary *)propertyInspectionForClass:(Class)objectClass } } - NSDictionary *propertyInspection = @{ RKPropertyInspectionNameKey: propNameString, - RKPropertyInspectionKeyValueCodingClassKey: aClass, - RKPropertyInspectionIsPrimitiveKey: @(isPrimitive) }; - [inspection setObject:propertyInspection forKey:propNameString]; + RKPropertyInspectorPropertyInfo *info; + info = [RKPropertyInspectorPropertyInfo propertyInfoWithName:propNameString + keyValueClass:aClass + isPrimitive:isPrimitive]; + [inspection setObject:info forKey:propNameString]; } } } @@ -136,9 +158,9 @@ - (NSDictionary *)propertyInspectionForClass:(Class)objectClass - (Class)classForPropertyNamed:(NSString *)propertyName ofClass:(Class)objectClass isPrimitive:(BOOL *)isPrimitive { NSDictionary *classInspection = [self propertyInspectionForClass:objectClass]; - NSDictionary *propertyInspection = [classInspection objectForKey:propertyName]; - if (isPrimitive) *isPrimitive = [[propertyInspection objectForKey:RKPropertyInspectionIsPrimitiveKey] boolValue]; - return [propertyInspection objectForKey:RKPropertyInspectionKeyValueCodingClassKey]; + RKPropertyInspectorPropertyInfo *propertyInspection = [classInspection objectForKey:propertyName]; + if (isPrimitive) *isPrimitive = propertyInspection.isPrimitive; + return propertyInspection.keyValueCodingClass; } @end @@ -152,10 +174,17 @@ @implementation NSObject (RKPropertyInspection) - (Class)rk_classForPropertyAtKeyPath:(NSString *)keyPath isPrimitive:(BOOL *)isPrimitive { - NSArray *components = [keyPath componentsSeparatedByString:@"."]; + NSRange dotRange = [keyPath rangeOfString:@"." options:NSLiteralSearch]; + RKPropertyInspector *inspector = [RKPropertyInspector sharedInspector]; Class propertyClass = [self class]; + + if (dotRange.length == 0) { + return [inspector classForPropertyNamed:keyPath ofClass:propertyClass isPrimitive:isPrimitive]; + } + + NSArray *components = [keyPath componentsSeparatedByString:@"."]; for (NSString *property in components) { - propertyClass = [[RKPropertyInspector sharedInspector] classForPropertyNamed:property ofClass:propertyClass isPrimitive:isPrimitive]; + propertyClass = [inspector classForPropertyNamed:property ofClass:propertyClass isPrimitive:isPrimitive]; if (! propertyClass) break; } diff --git a/Code/Support/RKDictionaryUtilities.m b/Code/Support/RKDictionaryUtilities.m index 6846abb9c0..7bd11977a7 100644 --- a/Code/Support/RKDictionaryUtilities.m +++ b/Code/Support/RKDictionaryUtilities.m @@ -15,17 +15,18 @@ NSMutableDictionary *mergedDictionary = [dict1 mutableCopy]; - [dict2 enumerateKeysAndObjectsUsingBlock:^(id key2, id obj2, BOOL *stop) { - id obj1 = [dict1 valueForKey:key2]; + for (id key2 in dict2) { + id obj2 = [dict2 objectForKey:key2]; + id obj1 = [dict1 objectForKey:key2]; if ([obj1 isKindOfClass:[NSDictionary class]] && [obj2 isKindOfClass:[NSDictionary class]]) { NSDictionary *mergedSubdict = RKDictionaryByMergingDictionaryWithDictionary(obj1, obj2); - [mergedDictionary setValue:mergedSubdict forKey:key2]; + [mergedDictionary setObject:mergedSubdict forKey:key2]; } else { - [mergedDictionary setValue:obj2 forKey:key2]; + [mergedDictionary setObject:obj2 forKey:key2]; } - }]; + } - return [mergedDictionary copy]; + return mergedDictionary; } NSDictionary *RKDictionaryByReplacingPercentEscapesInEntriesFromDictionary(NSDictionary *dictionary) diff --git a/Code/Support/RKLog.h b/Code/Support/RKLog.h index e701d754c5..da5de948f3 100644 --- a/Code/Support/RKLog.h +++ b/Code/Support/RKLog.h @@ -26,6 +26,30 @@ */ #import "lcl_RK.h" +/** + * Protocol which classes can implement to determine how RestKit log messages actually get handled. + * There is a single "current" logging class installed, which all log messages will flow + * through. + */ +@protocol RKLogging + ++ (void)logWithComponent:(_RKlcl_component_t)component + level:(_RKlcl_level_t)level + path:(const char *)file + line:(uint32_t)line + function:(const char *)function + format:(NSString *)format, ... NS_FORMAT_FUNCTION(6, 7); + +@end + +/** + * Functions to get and set the current RKLogging class. + */ +Class RKGetLoggingClass(void); +void RKSetLoggingClass(Class loggingClass); + + + /** RKLogComponent defines the active component within any given portion of RestKit diff --git a/Code/Support/RKLog.m b/Code/Support/RKLog.m index c6c71bf7f6..9179b53ac9 100644 --- a/Code/Support/RKLog.m +++ b/Code/Support/RKLog.m @@ -20,6 +20,21 @@ #import "RKLog.h" +@interface RKNSLogLogger : NSObject +@end + +#if RKLOG_USE_NSLOGGER && __has_include("LCLNSLogger_RK.h") + #import "LCLNSLogger_RK.h" + #define RKLOG_CLASS LCLNSLogger_RK + +#elif __has_include("DDLog.h") + #import "RKLumberjackLogger.h" + #define RKLOG_CLASS RKLumberjackLogger + +#else + #define RKLOG_CLASS RKNSLogLogger +#endif + // Hook into Objective-C runtime to configure logging when we are loaded @interface RKLogInitializer : NSObject @end @@ -30,11 +45,43 @@ + (void)load { RKlcl_configure_by_name("RestKit*", RKLogLevelDefault); RKlcl_configure_by_name("App", RKLogLevelDefault); + if (RKGetLoggingClass() == Nil) RKSetLoggingClass([RKLOG_CLASS class]); RKLogInfo(@"RestKit logging initialized..."); } @end +static Class RKLoggingClass; + +Class RKGetLoggingClass(void) +{ + return RKLoggingClass; +} + +void RKSetLoggingClass(Class loggingClass) +{ + RKLoggingClass = loggingClass; +} + +@implementation RKNSLogLogger + ++ (void)logWithComponent:(_RKlcl_component_t)component + level:(_RKlcl_level_t)level + path:(const char *)file + line:(uint32_t)line + function:(const char *)function + format:(NSString *)format, ... +{ + va_list args; + va_start(args, format); + NSString *message = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + const char *fileName = (fileName = strrchr(file, '/')) ? fileName + 1 : file; + NSLog(@"%s %s:%s:%d %@", _RKlcl_level_header_1[level], _RKlcl_component_header[component], fileName, line, message); +} + +@end + int RKLogLevelForString(NSString *, NSString *); void RKLogConfigureFromEnvironment(void) diff --git a/Code/Support/RKLumberjackLogger.h b/Code/Support/RKLumberjackLogger.h new file mode 100644 index 0000000000..c77def651a --- /dev/null +++ b/Code/Support/RKLumberjackLogger.h @@ -0,0 +1,17 @@ +// +// RKLumberjackLogger.h +// Pods +// +// Created by C_Lindberg,Carl on 10/31/14. +// +// + +#import + +#if __has_include("DDLog.h") +#import "RKLog.h" + +@interface RKLumberjackLogger : NSObject +@end + +#endif diff --git a/Code/Support/RKLumberjackLogger.m b/Code/Support/RKLumberjackLogger.m new file mode 100644 index 0000000000..34b2cc8ca7 --- /dev/null +++ b/Code/Support/RKLumberjackLogger.m @@ -0,0 +1,107 @@ +// +// RKLumberjackLogger.m +// Pods +// +// Created by C_Lindberg,Carl on 10/31/14. +// +// + +#if __has_include("DDLog.h") +#import "RKLumberjackLogger.h" +#import "DDLog.h" + +@implementation RKLumberjackLogger + ++ (int)ddLogLevelFromRKLogLevel:(_RKlcl_level_t)rkLevel +{ + switch (rkLevel) + { + case RKLogLevelOff: return LOG_LEVEL_OFF; + case RKLogLevelCritical: return LOG_LEVEL_ERROR; + case RKLogLevelError: return LOG_LEVEL_ERROR; + case RKLogLevelWarning: return LOG_LEVEL_WARN; + case RKLogLevelInfo: return LOG_LEVEL_INFO; + case RKLogLevelDebug: return LOG_LEVEL_DEBUG; + case RKLogLevelTrace: return LOG_LEVEL_VERBOSE; + } + + return LOG_LEVEL_DEBUG; +} + ++ (int)ddLogFlagFromRKLogLevel:(_RKlcl_level_t)rkLevel +{ + switch (rkLevel) + { + case RKLogLevelOff: return 0; + case RKLogLevelCritical: return LOG_FLAG_ERROR; + case RKLogLevelError: return LOG_FLAG_ERROR; + case RKLogLevelWarning: return LOG_FLAG_WARN; + case RKLogLevelInfo: return LOG_FLAG_INFO; + case RKLogLevelDebug: return LOG_FLAG_DEBUG; + case RKLogLevelTrace: return LOG_FLAG_VERBOSE; + } + + return LOG_FLAG_DEBUG; +} + ++ (_RKlcl_level_t)rkLogLevelFromDDLogLevel:(int)ddLogLevel +{ + if (ddLogLevel & LOG_FLAG_VERBOSE) return RKLogLevelTrace; + if (ddLogLevel & LOG_FLAG_DEBUG) return RKLogLevelDebug; + if (ddLogLevel & LOG_FLAG_INFO) return RKLogLevelInfo; + if (ddLogLevel & LOG_FLAG_WARN) return RKLogLevelWarning; + if (ddLogLevel & LOG_FLAG_ERROR) return RKLogLevelError; + + return RKLogLevelOff; +} + + +#pragma mark RKLogging + ++ (void)logWithComponent:(_RKlcl_component_t)component + level:(_RKlcl_level_t)level + path:(const char *)path + line:(uint32_t)line + function:(const char *)function + format:(NSString *)format, ... +{ + va_list args; + va_start(args, format); + + int flag = [self ddLogFlagFromRKLogLevel:level]; + int componentLevel = [self ddLogLevelFromRKLogLevel:_RKlcl_component_level[component]]; + BOOL async = LOG_ASYNC_ENABLED && ((flag & LOG_FLAG_ERROR) == 0); + + [DDLog log:async + level:componentLevel + flag:flag + context:0 /* Could define a special value here to identify RestKit logs to any backend loggers */ + file:path function:function line:line + tag:nil + format:format args:args]; + va_end(args); +} + +@end + +/* Create a DDRegisteredDynamicLogging class for each RestKit component */ + +#undef _RKlcl_component +#define _RKlcl_component(_identifier, _header, _name) \ + @interface RKLumberjackLog##_identifier : NSObject \ + @end \ + @implementation RKLumberjackLog##_identifier \ + + (int)ddLogLevel { \ + _RKlcl_level_t level = _RKlcl_component_level[RKlcl_c##_identifier]; \ + return [RKLumberjackLogger ddLogLevelFromRKLogLevel:level]; \ + } \ + + (void)ddSetLogLevel:(int)logLevel { \ + RKLogConfigureByName(_name, [RKLumberjackLogger rkLogLevelFromDDLogLevel:logLevel]); \ + } \ + @end + +#include "lcl_config_components_RK.h" +#undef _RKlcl_component + + +#endif diff --git a/Code/Support/lcl_config_logger_RK.h b/Code/Support/lcl_config_logger_RK.h index 230050264b..64e2ffaa25 100644 --- a/Code/Support/lcl_config_logger_RK.h +++ b/Code/Support/lcl_config_logger_RK.h @@ -18,10 +18,36 @@ // limitations under the License. // -// NSLog +// +// Integration with LibComponentLogging Core. +// + +// ARC/non-ARC autorelease pool +#define _RKlcl_logger_autoreleasepool_arc 0 +#if defined(__has_feature) +# if __has_feature(objc_arc) +# undef _RKlcl_logger_autoreleasepool_arc +# define _RKlcl_logger_autoreleasepool_arc 1 +# endif +#endif -#ifdef RKLOG_USE_NSLOGGER -#import "LCLNSLogger_RK.h" +#if _RKlcl_logger_autoreleasepool_arc + #define _RKlcl_logger_autoreleasepool_begin @autoreleasepool { + #define _RKlcl_logger_autoreleasepool_end } #else -#import "LCLNSLog_RK.h" + #define _RKlcl_logger_autoreleasepool_begin NSAutoreleasePool *_RKlcl_logpool = [[NSAutoreleasePool alloc] init]; + #define _RKlcl_logger_autoreleasepool_end [_RKlcl_logpool release]; #endif + + +#define _RKlcl_logger(_component, _level, _format, ...) { \ + _RKlcl_logger_autoreleasepool_begin \ + [RKGetLoggingClass() logWithComponent:_component \ + level:_level \ + path:__FILE__ \ + line:__LINE__ \ + function:__PRETTY_FUNCTION__ \ + format:_format, ## __VA_ARGS__]; \ + _RKlcl_logger_autoreleasepool_end \ +} + diff --git a/Code/Testing/RKMappingTest.m b/Code/Testing/RKMappingTest.m index e17b7de252..75e68d6ced 100644 --- a/Code/Testing/RKMappingTest.m +++ b/Code/Testing/RKMappingTest.m @@ -357,7 +357,6 @@ - (BOOL)event:(RKMappingTestEvent *)event satisfiesExpectation:(id)expectation e // Configure an operation queue to enable easy testing of connection operations NSOperationQueue *operationQueue = [NSOperationQueue new]; dataSource.operationQueue = operationQueue; - dataSource.parentOperation = mappingOperation; return dataSource; } else { return [RKObjectMappingOperationDataSource new]; diff --git a/Examples/RKTwitter/RKTwitter.xcodeproj/project.pbxproj b/Examples/RKTwitter/RKTwitter.xcodeproj/project.pbxproj index b69d3b844b..18dbabeb9e 100755 --- a/Examples/RKTwitter/RKTwitter.xcodeproj/project.pbxproj +++ b/Examples/RKTwitter/RKTwitter.xcodeproj/project.pbxproj @@ -95,6 +95,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 1BBF770CA495AD12361E6DA5 /* Pods-RKTwitterCocoaPods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RKTwitterCocoaPods.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RKTwitterCocoaPods/Pods-RKTwitterCocoaPods.debug.xcconfig"; sourceTree = ""; }; 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 1D3623240D0F684500981E51 /* RKTwitterAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKTwitterAppDelegate.h; sourceTree = ""; }; 1D3623250D0F684500981E51 /* RKTwitterAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKTwitterAppDelegate.m; sourceTree = ""; }; @@ -126,8 +127,8 @@ 7666CC5642F94148AF23256A /* libPods-RKTwitterCocoaPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RKTwitterCocoaPods.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 84F524C112824D5000C370EA /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 84F524C512824D5B00C370EA /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; - 89EB9DE2DCB14C1EB97E9637 /* Pods-RKTwitterCocoaPods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RKTwitterCocoaPods.xcconfig"; path = "Pods/Pods-RKTwitterCocoaPods.xcconfig"; sourceTree = SOURCE_ROOT; }; 8D1107310486CEB800E47090 /* RKTwitter-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "RKTwitter-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; + EC32B4B0491C719850D46F92 /* Pods-RKTwitterCocoaPods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RKTwitterCocoaPods.release.xcconfig"; path = "Pods/Target Support Files/Pods-RKTwitterCocoaPods/Pods-RKTwitterCocoaPods.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -209,7 +210,7 @@ 29B97317FDCFA39411CA2CEA /* Resources */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, - 89EB9DE2DCB14C1EB97E9637 /* Pods-RKTwitterCocoaPods.xcconfig */, + A54F9C18924391E57B4EFF4D /* Pods */, ); name = CustomTemplate; sourceTree = ""; @@ -255,6 +256,15 @@ name = Frameworks; sourceTree = ""; }; + A54F9C18924391E57B4EFF4D /* Pods */ = { + isa = PBXGroup; + children = ( + 1BBF770CA495AD12361E6DA5 /* Pods-RKTwitterCocoaPods.debug.xcconfig */, + EC32B4B0491C719850D46F92 /* Pods-RKTwitterCocoaPods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -341,7 +351,7 @@ 25160FB51456E8A30060A5C5 /* RestKitTests.octest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; - path = RestKitTests.octest; + path = RestKitTests.xctest; remoteRef = 25160FB41456E8A30060A5C5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -355,7 +365,7 @@ 25160FB91456E8A30060A5C5 /* RestKitFrameworkTests.octest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; - path = RestKitFrameworkTests.octest; + path = RestKitFrameworkTests.xctest; remoteRef = 25160FB81456E8A30060A5C5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -420,7 +430,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Pods-RKTwitterCocoaPods-resources.sh\"\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RKTwitterCocoaPods/Pods-RKTwitterCocoaPods-resources.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -504,7 +514,7 @@ }; 25AABD1217B69CC50061DC5B /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 89EB9DE2DCB14C1EB97E9637 /* Pods-RKTwitterCocoaPods.xcconfig */; + baseConfigurationReference = 1BBF770CA495AD12361E6DA5 /* Pods-RKTwitterCocoaPods.debug.xcconfig */; buildSettings = { BUILD_STYLE = Debug; CLANG_ENABLE_OBJC_ARC = YES; @@ -522,7 +532,7 @@ }; 25AABD1317B69CC50061DC5B /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 89EB9DE2DCB14C1EB97E9637 /* Pods-RKTwitterCocoaPods.xcconfig */; + baseConfigurationReference = EC32B4B0491C719850D46F92 /* Pods-RKTwitterCocoaPods.release.xcconfig */; buildSettings = { BUILD_STYLE = Release; CLANG_ENABLE_OBJC_ARC = YES; diff --git a/Gemfile b/Gemfile index ebd7181b62..f358e5c403 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,9 @@ source "http://rubygems.org" gem 'rakeup', '~> 1.2.0' -gem "bundler", "~> 1.6.1" -gem "sinatra", '~> 1.4.0' -gem "sinatra-contrib", '~> 1.4.0' -gem "thin", "~> 1.5.0" -gem 'cocoapods', '~> 0.33.1' +gem 'sinatra', '~> 1.4.0' +gem 'sinatra-contrib', '~> 1.4.0' +gem 'thin', '~> 1.5.0' +gem 'cocoapods', '~> 0.35.0' gem 'xctasks', '~> 0.2.1' gem 'xcpretty', '~> 0.1.6' diff --git a/Gemfile.lock b/Gemfile.lock index b3fd6f13ef..e6cbe64907 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,51 +1,54 @@ GEM remote: http://rubygems.org/ specs: - activesupport (3.2.18) - i18n (~> 0.6, >= 0.6.4) - multi_json (~> 1.0) - backports (3.3.5) - claide (0.6.1) - cocoapods (0.33.1) - activesupport (>= 3.2.15, < 4) - claide (~> 0.6.1) - cocoapods-core (= 0.33.1) - cocoapods-downloader (~> 0.6.1) - cocoapods-plugins (~> 0.2.0) - cocoapods-trunk (~> 0.1.1) - cocoapods-try (~> 0.3.0) + activesupport (4.1.8) + i18n (~> 0.6, >= 0.6.9) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.1) + tzinfo (~> 1.1) + backports (3.6.4) + claide (0.7.0) + cocoapods (0.35.0) + activesupport (>= 3.2.15) + claide (~> 0.7.0) + cocoapods-core (= 0.35.0) + cocoapods-downloader (~> 0.8.0) + cocoapods-plugins (~> 0.3.1) + cocoapods-trunk (~> 0.4.1) + cocoapods-try (~> 0.4.2) colored (~> 1.2) escape (~> 0.0.4) - json_pure (~> 1.8) - nap (~> 0.7) + molinillo (~> 0.1.2) + nap (~> 0.8) open4 (~> 1.3) - xcodeproj (~> 0.17.0) - cocoapods-core (0.33.1) + xcodeproj (~> 0.20.2) + cocoapods-core (0.35.0) activesupport (>= 3.2.15) fuzzy_match (~> 2.0.4) - json_pure (~> 1.8) - nap (~> 0.5) - cocoapods-downloader (0.6.1) - cocoapods-plugins (0.2.0) + nap (~> 0.8.0) + cocoapods-downloader (0.8.0) + cocoapods-plugins (0.3.2) nap - cocoapods-trunk (0.1.3) - json_pure (~> 1.8) - nap (>= 0.6) - netrc - cocoapods-try (0.3.0) + cocoapods-trunk (0.4.1) + nap (>= 0.8) + netrc (= 0.7.8) + cocoapods-try (0.4.2) colored (1.2) daemons (1.1.9) escape (0.0.4) eventmachine (1.0.3) fuzzy_match (2.0.4) - i18n (0.6.9) - json_pure (1.8.1) - multi_json (1.8.2) + i18n (0.6.11) + json (1.8.1) + minitest (5.4.3) + molinillo (0.1.2) + multi_json (1.10.1) nap (0.8.0) - netrc (0.7.7) + netrc (0.7.8) open4 (1.3.4) rack (1.5.2) - rack-protection (1.5.1) + rack-protection (1.5.3) rack rack-test (0.6.2) rack (>= 1.0) @@ -53,11 +56,11 @@ GEM rakeup (1.2.0) rack (~> 1.5.0) rake (~> 10.3.0) - sinatra (1.4.4) + sinatra (1.4.5) rack (~> 1.4) rack-protection (~> 1.4) tilt (~> 1.3, >= 1.3.4) - sinatra-contrib (1.4.1) + sinatra-contrib (1.4.2) backports (>= 2.0) multi_json rack-protection @@ -68,19 +71,21 @@ GEM daemons (>= 1.0.9) eventmachine (>= 0.12.6) rack (>= 1.0.0) + thread_safe (0.3.4) tilt (1.4.1) - xcodeproj (0.17.0) - activesupport (~> 3.0) + tzinfo (1.2.2) + thread_safe (~> 0.1) + xcodeproj (0.20.2) + activesupport (>= 3) colored (~> 1.2) - xcpretty (0.1.6) - xctasks (0.2.1) + xcpretty (0.1.7) + xctasks (0.2.2) PLATFORMS ruby DEPENDENCIES - bundler (~> 1.6.1) - cocoapods (~> 0.33.1) + cocoapods (~> 0.35.0) rakeup (~> 1.2.0) sinatra (~> 1.4.0) sinatra-contrib (~> 1.4.0) diff --git a/Podfile b/Podfile index 8d997000b9..5a87788340 100644 --- a/Podfile +++ b/Podfile @@ -1,3 +1,5 @@ +source 'https://github.com/CocoaPods/Specs.git' + inhibit_all_warnings! def import_pods @@ -6,7 +8,7 @@ def import_pods pod 'RestKit/Search', :path => '.' pod 'Specta', '0.2.1' - pod 'OCMock', '2.2.1' + pod 'OCMock', '2.2.4' pod 'OCHamcrest', '3.0.1' pod 'Expecta', '0.3.1' @@ -15,7 +17,7 @@ def import_pods end target :ios do - platform :ios, '5.0' + platform :ios, '5.1.1' link_with 'RestKitTests' import_pods end diff --git a/Podfile.lock b/Podfile.lock index d2bf6e9886..4cae19b31e 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -4,33 +4,33 @@ PODS: - ISO8601DateFormatterValueTransformer (0.6.0): - RKValueTransformers (~> 1.1.0) - OCHamcrest (3.0.1) - - OCMock (2.2.1) - - RestKit (0.23.3): - - RestKit/Core - - RestKit/Core (0.23.3): + - OCMock (2.2.4) + - RestKit (0.24.0): + - RestKit/Core (= 0.24.0) + - RestKit/Core (0.24.0): - RestKit/CoreData - RestKit/Network - RestKit/ObjectMapping - - RestKit/CoreData (0.23.3): + - RestKit/CoreData (0.24.0): - RestKit/ObjectMapping - - RestKit/Network (0.23.3): + - RestKit/Network (0.24.0): - AFNetworking (~> 1.3.0) - RestKit/ObjectMapping - RestKit/Support - SOCKit - - RestKit/ObjectMapping (0.23.3): + - RestKit/ObjectMapping (0.24.0): - ISO8601DateFormatterValueTransformer (~> 0.6.0) - RestKit/Support - RKValueTransformers (~> 1.1.0) - - RestKit/Search (0.23.3): + - RestKit/Search (0.24.0): - RestKit/CoreData - - RestKit/Support (0.23.3): + - RestKit/Support (0.24.0): - TransitionKit (= 2.1.0) - - RestKit/Testing (0.23.3): + - RestKit/Testing (0.24.0): - RestKit/Network - RKCLLocationValueTransformer (1.1.0): - RKValueTransformers (~> 1.1.0) - - RKValueTransformers (1.1.0) + - RKValueTransformers (1.1.1) - SOCKit (1.1) - Specta (0.2.1) - TransitionKit (2.1.0) @@ -38,7 +38,7 @@ PODS: DEPENDENCIES: - Expecta (= 0.3.1) - OCHamcrest (= 3.0.1) - - OCMock (= 2.2.1) + - OCMock (= 2.2.4) - RestKit (from `.`) - RestKit/Search (from `.`) - RestKit/Testing (from `.`) @@ -54,12 +54,12 @@ SPEC CHECKSUMS: Expecta: 03aabd0a89d8dea843baecb19a7fd7466a69a31d ISO8601DateFormatterValueTransformer: a705384ea6531255258e25b6480ba1180db703a0 OCHamcrest: 207233b7d7a44dadd66aca398947cc7e029c9be5 - OCMock: 0647d3c53ce945e6f1dbaf072a66848aeb376a40 - RestKit: ed18af01bf1fef0444cd6c4d11a6d0fb085979a8 + OCMock: 6db79185520e24f9f299548f2b8b07e41d881bd5 + RestKit: dcfcf0b3018837ad01b6123699274b606a0627d4 RKCLLocationValueTransformer: 4803de0b5bdd81a1ebedb93a8a1d715f92ceb3f4 - RKValueTransformers: b705e7b4651b9206b94600408462b74554bba969 + RKValueTransformers: 67b4ea3eb6a1c1a0b8c3c37eb4e4c05e4febeba2 SOCKit: d8d0dd3688ae1026c12367570da8fa8e1a6980fb Specta: 9141310f46b1f68b676650ff2854e1ed0b74163a TransitionKit: 6a6f3ddda38ae758778aa889dcb930e4f8d64edc -COCOAPODS: 0.33.1 +COCOAPODS: 0.35.0 diff --git a/README.md b/README.md index 2c56566a8c..e2dc2bfb22 100644 --- a/README.md +++ b/README.md @@ -324,7 +324,7 @@ RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithStr ### Manage a Queue of Object Request Operations ``` objective-c -RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]; +RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]]; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://restkit.org/articles/1234.json"]]; RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[responseDescriptor]]; @@ -509,7 +509,7 @@ fetchRequest.predicate = predicate; // Contains article1 due to body text containing 'match' NSArray *matches = [managedObjectStore.mainQueueManagedObjectContext executeFetchRequest:fetchRequest error:nil]; -NSLog(@"Found the matching articls: %@", matches); +NSLog(@"Found the matching articles: %@", matches); ``` ### Unit Test a Mapping diff --git a/Rakefile b/Rakefile index d9ba8b22a1..b58b2cc10d 100644 --- a/Rakefile +++ b/Rakefile @@ -16,11 +16,11 @@ XCTasks::TestTask.new(:test) do |t| t.schemes_dir = 'Tests/Schemes' t.runner = :xcpretty t.actions = %w{test} - + t.subtask(ios: 'RestKitTests') do |s| s.sdk = :iphonesimulator end - + t.subtask(osx: 'RestKitFrameworkTests') do |s| s.sdk = :macosx end @@ -31,8 +31,9 @@ task default: 'test' namespace :test do # Provides validation that RestKit continues to build without Core Data. This requires conditional compilation that is error prone task :building_without_core_data do - system("cd Examples/RKTwitter && pod install") - system("xctool -workspace Examples/RKTwitter/RKTwitter.xcworkspace -scheme RKTwitterCocoaPods -sdk iphonesimulator clean build ONLY_ACTIVE_ARCH=NO") + title 'Testing without Core Data' + run("cd Examples/RKTwitter && pod install") + run("xctool -workspace Examples/RKTwitter/RKTwitter.xcworkspace -scheme RKTwitterCocoaPods -sdk iphonesimulator clean build ONLY_ACTIVE_ARCH=NO") end end @@ -50,7 +51,7 @@ end def run(command, min_exit_status = 0) puts "Executing: `#{command}`" - system(command) + sh(command) if $?.exitstatus > min_exit_status puts "[!] Failed with exit code #{$?.exitstatus} while running: `#{command}`" exit($?.exitstatus) @@ -60,6 +61,7 @@ end desc "Build RestKit for iOS and Mac OS X" task :build do + title 'Building RestKit' run("xcodebuild -workspace RestKit.xcworkspace -scheme RestKit -sdk iphonesimulator5.0 clean build") run("xcodebuild -workspace RestKit.xcworkspace -scheme RestKit -sdk iphoneos clean build") run("xcodebuild -workspace RestKit.xcworkspace -scheme RestKit -sdk macosx10.6 clean build") @@ -83,7 +85,7 @@ namespace :docs do run(command, 1) puts "Generated HTML documentation at Docs/API/html" end - + desc "Check that documentation can be built from the source code via appledoc successfully." task :check => 'appledoc:check' do command = apple_doc_command << " --no-create-html --verbose 5 `find Code/ -name '*.h'`" @@ -97,21 +99,21 @@ namespace :docs do exit(exitstatus) else puts "!! appledoc generation failed with a fatal error" - end + end exit(exitstatus) end - + desc "Generate & install a docset into Xcode from the current sources" task :install => 'appledoc:check' do command = apple_doc_command << " --install-docset `find Code/ -name '*.h'`" run(command, 1) end - + desc "Build and publish the documentation set to the remote server (using rsync over SSH)" task :publish, :version, :destination, :publish_feed do |t, args| args.with_defaults(:version => File.read("VERSION").chomp, :destination => "restkit.org:/var/www/public/restkit.org/public/api/", :publish_feed => 'true') version = args[:version] - destination = args[:destination] + destination = args[:destination] puts "Generating RestKit docset for version #{version}..." command = apple_doc_command << " --keep-intermediate-files" << @@ -123,7 +125,7 @@ namespace :docs do versioned_destination = File.join(destination, version) command = "rsync -rvpPe ssh --delete Docs/API/html/ #{versioned_destination}" run(command) - + should_publish_feed = %{yes true 1}.include?(args[:publish_feed].downcase) if $?.exitstatus == 0 && should_publish_feed command = "rsync -rvpPe ssh Docs/API/publish/* #{destination}" @@ -138,10 +140,10 @@ namespace :build do ios_sdks = %w{iphonesimulator5.0 iphonesimulator6.0} osx_sdks = %w{macosx} osx_projects = %w{RKMacOSX} - + examples_path = File.join(File.expand_path(File.dirname(__FILE__)), 'Examples') example_projects = `find #{examples_path} -name '*.xcodeproj'`.split("\n") - puts "Building #{example_projects.size} Example projects..." + title "Building #{example_projects.size} Example projects..." example_projects.each do |example_project| project_name = File.basename(example_project).gsub('.xcodeproj', '') sdks = osx_projects.include?(project_name) ? osx_sdks : ios_sdks @@ -155,14 +157,23 @@ namespace :build do end desc "Validate a branch is ready for merging by checking for common issues" -task :validate => ['build:examples', 'docs:check', :test] do +task :validate => ['build:examples', 'docs:check', :test] do puts "Project state validated successfully. Proceed with merge." end task :lint do - system('bundle exec pod lib lint') + title 'Linting pod' + run('bundle exec pod lib lint') end desc 'Runs the CI suite' task :ci => ['server:start', :test, 'test:building_without_core_data', :lint] +def title(title) + cyan_title = "\033[0;36m#{title}\033[0m" + puts + puts "-" * 80 + puts cyan_title + puts "-" * 80 + puts +end diff --git a/RestKit.podspec b/RestKit.podspec index 690fcad2d6..1481a14592 100644 --- a/RestKit.podspec +++ b/RestKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'RestKit' - s.version = '0.23.3' + s.version = '0.24.0' s.summary = 'RestKit is a framework for consuming and modeling RESTful web resources on iOS and OS X.' s.homepage = 'http://www.restkit.org' s.social_media_url = 'https://twitter.com/RestKit' @@ -10,7 +10,7 @@ Pod::Spec.new do |s| # Platform setup s.requires_arc = true - s.ios.deployment_target = '5.0' + s.ios.deployment_target = '5.1.1' s.osx.deployment_target = '10.7' # Exclude optional Search and Testing modules @@ -99,7 +99,7 @@ EOS end s.subspec 'Support' do |ss| - ss.source_files = 'Code/RestKit.h', 'Code/Support.h', 'Code/Support', 'Vendor/LibComponentLogging/Core', 'Vendor/LibComponentLogging/NSLog' + ss.source_files = 'Code/RestKit.h', 'Code/Support.h', 'Code/Support', 'Vendor/LibComponentLogging/Core' ss.dependency 'TransitionKit', '2.1.0' end end diff --git a/RestKit.xcodeproj/project.pbxproj b/RestKit.xcodeproj/project.pbxproj index bf9e46e816..88cad46df0 100644 --- a/RestKit.xcodeproj/project.pbxproj +++ b/RestKit.xcodeproj/project.pbxproj @@ -60,7 +60,6 @@ 25119FB6154A34B400C6BC58 /* parents_and_children.json in Resources */ = {isa = PBXBuildFile; fileRef = 25119FB5154A34B400C6BC58 /* parents_and_children.json */; }; 25119FB7154A34B400C6BC58 /* parents_and_children.json in Resources */ = {isa = PBXBuildFile; fileRef = 25119FB5154A34B400C6BC58 /* parents_and_children.json */; }; 25160D1A14564E810060A5C5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25160D1914564E810060A5C5 /* Foundation.framework */; }; - 25160D2814564E820060A5C5 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25160D2714564E820060A5C5 /* SenTestingKit.framework */; }; 25160D2A14564E820060A5C5 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25160D2914564E820060A5C5 /* UIKit.framework */; }; 25160D2B14564E820060A5C5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25160D1914564E810060A5C5 /* Foundation.framework */; }; 25160DDB145650490060A5C5 /* RKEntityMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 25160D4C145650490060A5C5 /* RKEntityMapping.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -103,16 +102,11 @@ 25160E4C145650490060A5C5 /* RKMIMETypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 25160DC3145650490060A5C5 /* RKMIMETypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25160E4D145650490060A5C5 /* RKMIMETypes.m in Sources */ = {isa = PBXBuildFile; fileRef = 25160DC4145650490060A5C5 /* RKMIMETypes.m */; }; 25160E4E145650490060A5C5 /* RKSerialization.h in Headers */ = {isa = PBXBuildFile; fileRef = 25160DC5145650490060A5C5 /* RKSerialization.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 25160E79145651060060A5C5 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25160D2714564E820060A5C5 /* SenTestingKit.framework */; }; 25160E7A145651060060A5C5 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25160E63145651060060A5C5 /* Cocoa.framework */; }; 25160EE21456532C0060A5C5 /* lcl_RK.h in Headers */ = {isa = PBXBuildFile; fileRef = 25160EA21456532C0060A5C5 /* lcl_RK.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25160EE31456532C0060A5C5 /* lcl_RK.h in Headers */ = {isa = PBXBuildFile; fileRef = 25160EA21456532C0060A5C5 /* lcl_RK.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25160EE41456532C0060A5C5 /* lcl_RK.m in Sources */ = {isa = PBXBuildFile; fileRef = 25160EA31456532C0060A5C5 /* lcl_RK.m */; }; 25160EE51456532C0060A5C5 /* lcl_RK.m in Sources */ = {isa = PBXBuildFile; fileRef = 25160EA31456532C0060A5C5 /* lcl_RK.m */; }; - 25160EF81456532C0060A5C5 /* LCLNSLog_RK.h in Headers */ = {isa = PBXBuildFile; fileRef = 25160EB01456532C0060A5C5 /* LCLNSLog_RK.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 25160EF91456532C0060A5C5 /* LCLNSLog_RK.h in Headers */ = {isa = PBXBuildFile; fileRef = 25160EB01456532C0060A5C5 /* LCLNSLog_RK.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 25160EFA1456532C0060A5C5 /* LCLNSLog_RK.m in Sources */ = {isa = PBXBuildFile; fileRef = 25160EB11456532C0060A5C5 /* LCLNSLog_RK.m */; }; - 25160EFB1456532C0060A5C5 /* LCLNSLog_RK.m in Sources */ = {isa = PBXBuildFile; fileRef = 25160EB11456532C0060A5C5 /* LCLNSLog_RK.m */; }; 25160F081456532C0060A5C5 /* SOCKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 25160EBD1456532C0060A5C5 /* SOCKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25160F091456532C0060A5C5 /* SOCKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 25160EBD1456532C0060A5C5 /* SOCKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25160F0A1456532C0060A5C5 /* SOCKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 25160EBE1456532C0060A5C5 /* SOCKit.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; @@ -607,6 +601,10 @@ C0F11CE6190883460054AEA0 /* RKPathMatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = C0F11CE2190883380054AEA0 /* RKPathMatcher.m */; }; C0F11CE81908C7E60054AEA0 /* RKCoreData.h in Headers */ = {isa = PBXBuildFile; fileRef = C0F11CE71908C7E60054AEA0 /* RKCoreData.h */; settings = {ATTRIBUTES = (Public, ); }; }; C0F11CE91908C7E60054AEA0 /* RKCoreData.h in Headers */ = {isa = PBXBuildFile; fileRef = C0F11CE71908C7E60054AEA0 /* RKCoreData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DB1148441A0B26B100C8A00A /* RKLumberjackLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = DB1148421A0B26B100C8A00A /* RKLumberjackLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DB1148451A0B26B100C8A00A /* RKLumberjackLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = DB1148421A0B26B100C8A00A /* RKLumberjackLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DB1148461A0B26B100C8A00A /* RKLumberjackLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = DB1148431A0B26B100C8A00A /* RKLumberjackLogger.m */; }; + DB1148471A0B26B100C8A00A /* RKLumberjackLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = DB1148431A0B26B100C8A00A /* RKLumberjackLogger.m */; }; E2F2B89961FC462B981CEB7A /* libPods-ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E457EFC3D502479D8B4FCF2A /* libPods-ios.a */; }; /* End PBXBuildFile section */ @@ -640,6 +638,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 04D69B4B2A54475834B333CF /* Pods-osx.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-osx.release.xcconfig"; path = "Pods/Target Support Files/Pods-osx/Pods-osx.release.xcconfig"; sourceTree = ""; }; + 061E3E4524CE8EF78257C26C /* Pods-ios.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios.release.xcconfig"; path = "Pods/Target Support Files/Pods-ios/Pods-ios.release.xcconfig"; sourceTree = ""; }; + 147F950728350A64F103F55D /* Pods-ios.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ios/Pods-ios.debug.xcconfig"; sourceTree = ""; }; 2501405215366000004E0466 /* RKObjectiveCppTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RKObjectiveCppTest.mm; sourceTree = ""; }; 2502C8E715F79CF70060FD75 /* CoreData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreData.h; sourceTree = ""; }; 2502C8E815F79CF70060FD75 /* Network.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Network.h; sourceTree = ""; }; @@ -669,8 +670,7 @@ 25119FB5154A34B400C6BC58 /* parents_and_children.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = parents_and_children.json; sourceTree = ""; }; 25160D1614564E810060A5C5 /* libRestKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRestKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; 25160D1914564E810060A5C5 /* Foundation.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 25160D2614564E820060A5C5 /* RestKitTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RestKitTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; - 25160D2714564E820060A5C5 /* SenTestingKit.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; + 25160D2614564E820060A5C5 /* RestKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RestKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 25160D2914564E820060A5C5 /* UIKit.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 25160D4C145650490060A5C5 /* RKEntityMapping.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKEntityMapping.h; sourceTree = ""; }; 25160D4D145650490060A5C5 /* RKEntityMapping.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKEntityMapping.m; sourceTree = ""; }; @@ -717,12 +717,10 @@ 25160E66145651060060A5C5 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 25160E67145651060060A5C5 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 25160E68145651060060A5C5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 25160E78145651060060A5C5 /* RestKitFrameworkTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RestKitFrameworkTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; + 25160E78145651060060A5C5 /* RestKitFrameworkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RestKitFrameworkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 25160EA21456532C0060A5C5 /* lcl_RK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lcl_RK.h; sourceTree = ""; }; 25160EA31456532C0060A5C5 /* lcl_RK.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = lcl_RK.m; sourceTree = ""; }; 25160EA71456532C0060A5C5 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = ""; }; - 25160EB01456532C0060A5C5 /* LCLNSLog_RK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LCLNSLog_RK.h; sourceTree = ""; }; - 25160EB11456532C0060A5C5 /* LCLNSLog_RK.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LCLNSLog_RK.m; sourceTree = ""; }; 25160EBD1456532C0060A5C5 /* SOCKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SOCKit.h; sourceTree = ""; }; 25160EBE1456532C0060A5C5 /* SOCKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SOCKit.m; sourceTree = ""; }; 25160F161456538B0060A5C5 /* libxml2.dylib */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = usr/lib/libxml2.dylib; sourceTree = SDKROOT; }; @@ -983,13 +981,14 @@ 73D3907314CA1A4A0093E3D6 /* child.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = child.json; sourceTree = ""; }; 73D3907814CA1D710093E3D6 /* channels.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = channels.xml; sourceTree = ""; }; 86EC453810D648768BF62304 /* libPods-osx.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-osx.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 8F2DFE1E847347368405F3B5 /* Pods-ios.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios.xcconfig"; path = "Pods/Pods-ios.xcconfig"; sourceTree = SOURCE_ROOT; }; BE05BDCF1782109F00F7C9C9 /* RKRouteTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKRouteTest.m; sourceTree = ""; }; C0F11CE1190883380054AEA0 /* RKPathMatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKPathMatcher.h; sourceTree = ""; }; C0F11CE2190883380054AEA0 /* RKPathMatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKPathMatcher.m; sourceTree = ""; }; C0F11CE71908C7E60054AEA0 /* RKCoreData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKCoreData.h; sourceTree = ""; }; + D8EFCBE1978F7946E0081FAD /* Pods-osx.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-osx.debug.xcconfig"; path = "Pods/Target Support Files/Pods-osx/Pods-osx.debug.xcconfig"; sourceTree = ""; }; + DB1148421A0B26B100C8A00A /* RKLumberjackLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKLumberjackLogger.h; sourceTree = ""; }; + DB1148431A0B26B100C8A00A /* RKLumberjackLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKLumberjackLogger.m; sourceTree = ""; }; E457EFC3D502479D8B4FCF2A /* libPods-ios.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ios.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - ED192D1B86AB47D7927731B0 /* Pods-osx.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-osx.xcconfig"; path = "Pods/Pods-osx.xcconfig"; sourceTree = SOURCE_ROOT; }; F66056291744FF9000A87A45 /* and_cats.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = and_cats.json; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1015,7 +1014,6 @@ 2516112C1456F51D0060A5C5 /* libxml2.dylib in Frameworks */, 2516112B1456F5170060A5C5 /* CFNetwork.framework in Frameworks */, 251611291456F50F0060A5C5 /* SystemConfiguration.framework in Frameworks */, - 25160D2814564E820060A5C5 /* SenTestingKit.framework in Frameworks */, 25160D2A14564E820060A5C5 /* UIKit.framework in Frameworks */, 25160D2B14564E820060A5C5 /* Foundation.framework in Frameworks */, E2F2B89961FC462B981CEB7A /* libPods-ios.a in Frameworks */, @@ -1038,7 +1036,6 @@ files = ( 25565959161FC3CD00F5BB20 /* SystemConfiguration.framework in Frameworks */, 25565956161FC3C300F5BB20 /* CoreServices.framework in Frameworks */, - 25160E79145651060060A5C5 /* SenTestingKit.framework in Frameworks */, 25160E7A145651060060A5C5 /* Cocoa.framework in Frameworks */, 7F9CBC6174004E31AEC35813 /* libPods-osx.a in Frameworks */, ); @@ -1047,6 +1044,17 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 15BB5BBD8E8B9B2C9C8B11BC /* Pods */ = { + isa = PBXGroup; + children = ( + 147F950728350A64F103F55D /* Pods-ios.debug.xcconfig */, + 061E3E4524CE8EF78257C26C /* Pods-ios.release.xcconfig */, + D8EFCBE1978F7946E0081FAD /* Pods-osx.debug.xcconfig */, + 04D69B4B2A54475834B333CF /* Pods-osx.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; 25104F0E15C30C7900829135 /* Search */ = { isa = PBXGroup; children = ( @@ -1083,8 +1091,7 @@ 25160E8D145652E40060A5C5 /* Vendor */, 25160D1814564E810060A5C5 /* Frameworks */, 25160D1714564E810060A5C5 /* Products */, - 8F2DFE1E847347368405F3B5 /* Pods-ios.xcconfig */, - ED192D1B86AB47D7927731B0 /* Pods-osx.xcconfig */, + 15BB5BBD8E8B9B2C9C8B11BC /* Pods */, ); sourceTree = ""; }; @@ -1092,9 +1099,9 @@ isa = PBXGroup; children = ( 25160D1614564E810060A5C5 /* libRestKit.a */, - 25160D2614564E820060A5C5 /* RestKitTests.octest */, + 25160D2614564E820060A5C5 /* RestKitTests.xctest */, 25160E62145651060060A5C5 /* RestKit.framework */, - 25160E78145651060060A5C5 /* RestKitFrameworkTests.octest */, + 25160E78145651060060A5C5 /* RestKitFrameworkTests.xctest */, ); name = Products; sourceTree = ""; @@ -1116,7 +1123,6 @@ 25160F7B145657220060A5C5 /* SystemConfiguration.framework */, 25160F161456538B0060A5C5 /* libxml2.dylib */, 25160D1914564E810060A5C5 /* Foundation.framework */, - 25160D2714564E820060A5C5 /* SenTestingKit.framework */, 25160D2914564E820060A5C5 /* UIKit.framework */, 25160E63145651060060A5C5 /* Cocoa.framework */, 25EC1B0014F8078100C3CF3F /* CoreFoundation.framework */, @@ -1245,6 +1251,8 @@ 25160DBF145650490060A5C5 /* RKDotNetDateFormatter.m */, 25160DC1145650490060A5C5 /* RKLog.h */, 25160DC2145650490060A5C5 /* RKLog.m */, + DB1148421A0B26B100C8A00A /* RKLumberjackLogger.h */, + DB1148431A0B26B100C8A00A /* RKLumberjackLogger.m */, 25160DC3145650490060A5C5 /* RKMIMETypes.h */, 25160DC4145650490060A5C5 /* RKMIMETypes.m */, 25160DC5145650490060A5C5 /* RKSerialization.h */, @@ -1288,7 +1296,6 @@ isa = PBXGroup; children = ( 25160EA11456532C0060A5C5 /* Core */, - 25160EAE1456532C0060A5C5 /* NSLog */, ); path = LibComponentLogging; sourceTree = ""; @@ -1303,15 +1310,6 @@ path = Core; sourceTree = ""; }; - 25160EAE1456532C0060A5C5 /* NSLog */ = { - isa = PBXGroup; - children = ( - 25160EB01456532C0060A5C5 /* LCLNSLog_RK.h */, - 25160EB11456532C0060A5C5 /* LCLNSLog_RK.m */, - ); - path = NSLog; - sourceTree = ""; - }; 25160EB71456532C0060A5C5 /* SOCKit */ = { isa = PBXGroup; children = ( @@ -1786,6 +1784,7 @@ 252CCE7217E0CA2700B7F0BF /* ISO8601DateFormatterValueTransformer.h in Headers */, 25160E0F145650490060A5C5 /* RKAttributeMapping.h in Headers */, 25160E16145650490060A5C5 /* RKMapperOperation.h in Headers */, + DB1148441A0B26B100C8A00A /* RKLumberjackLogger.h in Headers */, 25160E18145650490060A5C5 /* RKMapperOperation_Private.h in Headers */, 25160E1A145650490060A5C5 /* RKObjectMapping.h in Headers */, 25160E1C145650490060A5C5 /* RKMapping.h in Headers */, @@ -1803,7 +1802,6 @@ 25160E4C145650490060A5C5 /* RKMIMETypes.h in Headers */, 25160E4E145650490060A5C5 /* RKSerialization.h in Headers */, 25160EE21456532C0060A5C5 /* lcl_RK.h in Headers */, - 25160EF81456532C0060A5C5 /* LCLNSLog_RK.h in Headers */, 25160F081456532C0060A5C5 /* SOCKit.h in Headers */, 25160E2E145650490060A5C5 /* RestKit.h in Headers */, 25B408261491CDDC00F21111 /* RKPathUtilities.h in Headers */, @@ -1890,7 +1888,6 @@ buildActionMask = 2147483647; files = ( 25160EE31456532C0060A5C5 /* lcl_RK.h in Headers */, - 25160EF91456532C0060A5C5 /* LCLNSLog_RK.h in Headers */, 25160F091456532C0060A5C5 /* SOCKit.h in Headers */, 25160F44145655C60060A5C5 /* RKDynamicMapping.h in Headers */, 25160F46145655C60060A5C5 /* RKErrorMessage.h in Headers */, @@ -1898,6 +1895,7 @@ 252CCE7317E0CA2700B7F0BF /* ISO8601DateFormatterValueTransformer.h in Headers */, 25160F51145655C60060A5C5 /* RKMapperOperation.h in Headers */, 25160F53145655C60060A5C5 /* RKMapperOperation_Private.h in Headers */, + DB1148451A0B26B100C8A00A /* RKLumberjackLogger.h in Headers */, 25160F55145655C60060A5C5 /* RKObjectMapping.h in Headers */, 25160F57145655C60060A5C5 /* RKMapping.h in Headers */, 25160F58145655C60060A5C5 /* RKMappingOperation.h in Headers */, @@ -2034,8 +2032,8 @@ ); name = RestKitTests; productName = RestKitTests; - productReference = 25160D2614564E820060A5C5 /* RestKitTests.octest */; - productType = "com.apple.product-type.bundle"; + productReference = 25160D2614564E820060A5C5 /* RestKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; }; 25160E61145651060060A5C5 /* RestKitFramework */ = { isa = PBXNativeTarget; @@ -2073,8 +2071,8 @@ ); name = RestKitFrameworkTests; productName = RestKitFrameworkTests; - productReference = 25160E78145651060060A5C5 /* RestKitFrameworkTests.octest */; - productType = "com.apple.product-type.bundle"; + productReference = 25160E78145651060060A5C5 /* RestKitFrameworkTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -2083,7 +2081,7 @@ isa = PBXProject; attributes = { LastTestingUpgradeCheck = 0510; - LastUpgradeCheck = 0510; + LastUpgradeCheck = 0600; ORGANIZATIONNAME = RestKit; }; buildConfigurationList = 25160D1014564E810060A5C5 /* Build configuration list for PBXProject "RestKit" */; @@ -2217,7 +2215,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Pods-osx-resources.sh\"\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-osx/Pods-osx-resources.sh\"\n"; }; 212C92B936E847348494D6F7 /* Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; @@ -2231,7 +2229,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Pods-ios-resources.sh\"\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ios/Pods-ios-resources.sh\"\n"; }; 25160D2414564E820060A5C5 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -2272,6 +2270,7 @@ 25160DE2145650490060A5C5 /* RKManagedObjectStore.m in Sources */, 25160DE6145650490060A5C5 /* RKPropertyInspector+CoreData.m in Sources */, 25160E0A145650490060A5C5 /* RKDynamicMapping.m in Sources */, + DB1148461A0B26B100C8A00A /* RKLumberjackLogger.m in Sources */, 25160E0C145650490060A5C5 /* RKErrorMessage.m in Sources */, 25DA35711836741D001A56A0 /* TKTransition.m in Sources */, 25160E10145650490060A5C5 /* RKAttributeMapping.m in Sources */, @@ -2287,7 +2286,6 @@ 25160E4B145650490060A5C5 /* RKLog.m in Sources */, 25160E4D145650490060A5C5 /* RKMIMETypes.m in Sources */, 25160EE41456532C0060A5C5 /* lcl_RK.m in Sources */, - 25160EFA1456532C0060A5C5 /* LCLNSLog_RK.m in Sources */, 25160F0A1456532C0060A5C5 /* SOCKit.m in Sources */, 25B408281491CDDC00F21111 /* RKPathUtilities.m in Sources */, 25B6E95814CF7A1C00B1E881 /* RKErrors.m in Sources */, @@ -2428,11 +2426,11 @@ buildActionMask = 2147483647; files = ( 25160EE51456532C0060A5C5 /* lcl_RK.m in Sources */, - 25160EFB1456532C0060A5C5 /* LCLNSLog_RK.m in Sources */, 25160F0B1456532C0060A5C5 /* SOCKit.m in Sources */, 25160F45145655C60060A5C5 /* RKDynamicMapping.m in Sources */, 25160F47145655C60060A5C5 /* RKErrorMessage.m in Sources */, 25160F4B145655C60060A5C5 /* RKAttributeMapping.m in Sources */, + DB1148471A0B26B100C8A00A /* RKLumberjackLogger.m in Sources */, 25160F52145655C60060A5C5 /* RKMapperOperation.m in Sources */, 25DA35721836741D001A56A0 /* TKTransition.m in Sources */, 25160F56145655C60060A5C5 /* RKObjectMapping.m in Sources */, @@ -2612,8 +2610,10 @@ CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -2648,8 +2648,10 @@ CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -2705,7 +2707,7 @@ }; 25160D3E14564E820060A5C5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8F2DFE1E847347368405F3B5 /* Pods-ios.xcconfig */; + baseConfigurationReference = 147F950728350A64F103F55D /* Pods-ios.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_ARC = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -2717,20 +2719,15 @@ GCC_PREFIX_HEADER = "Code/Support/RestKit-Prefix.pch"; INFOPLIST_FILE = "Resources/PLISTs/RestKitTests-Info.plist"; OBJROOT = "$(SRCROOT)/Build"; - OTHER_LDFLAGS = ( - "$(inherited)", - "-framework", - SenTestingKit, - ); + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_NAME = "$(TARGET_NAME)"; SYMROOT = "$(SRCROOT)/Build/Products"; - WRAPPER_EXTENSION = octest; }; name = Debug; }; 25160D3F14564E820060A5C5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8F2DFE1E847347368405F3B5 /* Pods-ios.xcconfig */; + baseConfigurationReference = 061E3E4524CE8EF78257C26C /* Pods-ios.release.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_ARC = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -2742,14 +2739,9 @@ GCC_PREFIX_HEADER = "Code/Support/RestKit-Prefix.pch"; INFOPLIST_FILE = "Resources/PLISTs/RestKitTests-Info.plist"; OBJROOT = "$(SRCROOT)/Build"; - OTHER_LDFLAGS = ( - "$(inherited)", - "-framework", - SenTestingKit, - ); + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_NAME = "$(TARGET_NAME)"; SYMROOT = "$(SRCROOT)/Build/Products"; - WRAPPER_EXTENSION = octest; }; name = Release; }; @@ -2811,7 +2803,7 @@ }; 25160E8B145651060060A5C5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = ED192D1B86AB47D7927731B0 /* Pods-osx.xcconfig */; + baseConfigurationReference = D8EFCBE1978F7946E0081FAD /* Pods-osx.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_ARC = YES; COMBINE_HIDPI_IMAGES = YES; @@ -2825,13 +2817,12 @@ ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - WRAPPER_EXTENSION = octest; }; name = Debug; }; 25160E8C145651060060A5C5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = ED192D1B86AB47D7927731B0 /* Pods-osx.xcconfig */; + baseConfigurationReference = 04D69B4B2A54475834B333CF /* Pods-osx.release.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_ARC = YES; COMBINE_HIDPI_IMAGES = YES; @@ -2845,7 +2836,6 @@ MACOSX_DEPLOYMENT_TARGET = 10.7; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - WRAPPER_EXTENSION = octest; }; name = Release; }; diff --git a/RestKit.xcworkspace/xcshareddata/xcschemes/Build All Examples.xcscheme b/RestKit.xcworkspace/xcshareddata/xcschemes/Build All Examples.xcscheme index d18b3690e1..b248df1b48 100644 --- a/RestKit.xcworkspace/xcshareddata/xcschemes/Build All Examples.xcscheme +++ b/RestKit.xcworkspace/xcshareddata/xcschemes/Build All Examples.xcscheme @@ -1,6 +1,6 @@ + + + + diff --git a/Tests/Logic/CoreData/NSManagedObjectContext+RKAdditionsTest.m b/Tests/Logic/CoreData/NSManagedObjectContext+RKAdditionsTest.m index 5315d6288d..55ee270f50 100644 --- a/Tests/Logic/CoreData/NSManagedObjectContext+RKAdditionsTest.m +++ b/Tests/Logic/CoreData/NSManagedObjectContext+RKAdditionsTest.m @@ -10,7 +10,7 @@ #import "NSManagedObjectContext+RKAdditions.h" #import "RKHuman.h" -@interface NSManagedObjectContext_RKAdditionsTest : SenTestCase +@interface NSManagedObjectContext_RKAdditionsTest : XCTestCase @end diff --git a/Tests/Logic/CoreData/RKEntityMappingTest.m b/Tests/Logic/CoreData/RKEntityMappingTest.m index 8560dde70b..2563b97885 100644 --- a/Tests/Logic/CoreData/RKEntityMappingTest.m +++ b/Tests/Logic/CoreData/RKEntityMappingTest.m @@ -121,7 +121,7 @@ - (void)testShouldIncludeTransformableAttributesInPropertyNamesAndTypes assertThat([relationshipsByName objectForKey:@"favoriteColors"], is(nilValue())); NSDictionary *propertyNamesAndTypes = [[RKPropertyInspector sharedInspector] propertyInspectionForEntity:entity]; - assertThat([propertyNamesAndTypes objectForKey:@"favoriteColors"][RKPropertyInspectionKeyValueCodingClassKey], is(notNilValue())); + assertThat([(RKPropertyInspectorPropertyInfo *)[propertyNamesAndTypes objectForKey:@"favoriteColors"] keyValueCodingClass], is(notNilValue())); } - (void)testMappingAnArrayToATransformableWithoutABackingManagedObjectSubclass diff --git a/Tests/Logic/CoreData/RKManagedObjectLoaderTest.m b/Tests/Logic/CoreData/RKManagedObjectLoaderTest.m index 30be779024..a58edd2114 100644 --- a/Tests/Logic/CoreData/RKManagedObjectLoaderTest.m +++ b/Tests/Logic/CoreData/RKManagedObjectLoaderTest.m @@ -244,8 +244,7 @@ - (void)testThatObjectLoadedDidFinishLoadingIsCalledOnStoreSaveFailure RKObjectManager *objectManager = [RKTestFactory objectManager]; objectManager.managedObjectStore = managedObjectStore; id mockStore = [OCMockObject partialMockForObject:managedObjectStore]; - BOOL success = NO; - [[[mockStore stub] andReturnValue:OCMOCK_VALUE(success)] save:[OCMArg anyPointer]]; + [[[mockStore stub] andReturnValue:@NO] save:[OCMArg anyPointer]]; RKObjectMapping *mapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; RKManagedObjectLoader *objectLoader = [objectManager loaderWithResourcePath:@"/humans/1"]; diff --git a/Tests/Logic/CoreData/RKRelationshipConnectionOperationTest.m b/Tests/Logic/CoreData/RKRelationshipConnectionOperationTest.m index 63d64eaf43..093a0fa7f6 100644 --- a/Tests/Logic/CoreData/RKRelationshipConnectionOperationTest.m +++ b/Tests/Logic/CoreData/RKRelationshipConnectionOperationTest.m @@ -15,7 +15,7 @@ #import "RKRelationshipConnectionOperation.h" #import "RKFetchRequestManagedObjectCache.h" -@interface RKRelationshipConnectionOperationTest : SenTestCase +@interface RKRelationshipConnectionOperationTest : XCTestCase @end diff --git a/Tests/Logic/Network/RKHTTPRequestOperationTest.m b/Tests/Logic/Network/RKHTTPRequestOperationTest.m index 12e91365a8..2e772f4f43 100644 --- a/Tests/Logic/Network/RKHTTPRequestOperationTest.m +++ b/Tests/Logic/Network/RKHTTPRequestOperationTest.m @@ -9,7 +9,7 @@ #import "RKTestEnvironment.h" #import "RKHTTPRequestOperation.h" -@interface RKHTTPRequestOperationTest : SenTestCase +@interface RKHTTPRequestOperationTest : XCTestCase @end diff --git a/Tests/Logic/Network/RKObjectRequestOperationTest.m b/Tests/Logic/Network/RKObjectRequestOperationTest.m index d2ad37cb98..cadb6ac947 100644 --- a/Tests/Logic/Network/RKObjectRequestOperationTest.m +++ b/Tests/Logic/Network/RKObjectRequestOperationTest.m @@ -103,7 +103,7 @@ - (void)testShouldReturnSuccessWhenTheStatusCodeIs200AndTheResponseBodyOnlyConta - (void)testSendingAnObjectRequestOperationToAnInvalidHostname { - NSMutableURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://invalid.is"]]; + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://invalid.is"]]; RKObjectRequestOperation *requestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[ [self responseDescriptorForComplexUser] ]]; [requestOperation start]; expect([requestOperation isFinished]).will.beTruthy(); @@ -115,12 +115,14 @@ - (void)testSendingAnObjectRequestOperationToAnInvalidHostname - (void)testSendingAnObjectRequestOperationToAnBrokenURL { - NSMutableURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://invalid••™¡.is"]]; + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://invalid••™¡.is"]]; RKObjectRequestOperation *requestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[ [self responseDescriptorForComplexUser] ]]; [requestOperation start]; expect([requestOperation isFinished]).will.beTruthy(); - expect([requestOperation.error code]).to.equal(NSURLErrorBadURL); + // iOS8 (and presumably 10.10) returns NSURLErrorUnsupportedURL which means the HTTP NSURLProtocol does not accept it + NSArray *validErrorCodes = @[ @(NSURLErrorBadURL), @(NSURLErrorUnsupportedURL) ]; + expect(validErrorCodes).to.contain(requestOperation.error.code); } #pragma mark - Complex JSON diff --git a/Tests/Logic/Network/RKRouteSetTest.m b/Tests/Logic/Network/RKRouteSetTest.m index 551d9ef154..c6b28bc5a8 100644 --- a/Tests/Logic/Network/RKRouteSetTest.m +++ b/Tests/Logic/Network/RKRouteSetTest.m @@ -61,7 +61,7 @@ - (void)testCannotAddARouteThatIsAlreadyAdded RKRouteSet *router = [RKRouteSet new]; RKRoute *route = [RKRoute routeWithName:@"test_router" pathPattern:@"/routes" method:RKRequestMethodGET]; [router addRoute:route]; - STAssertThrowsSpecificNamed([router addRoute:route], NSException, NSInternalInconsistencyException, @"Cannot add a route that is already added to the router."); + XCTAssertThrowsSpecificNamed([router addRoute:route], NSException, NSInternalInconsistencyException, @"Cannot add a route that is already added to the router."); } - (void)testCannotAddARouteWithAnExistingName @@ -70,7 +70,7 @@ - (void)testCannotAddARouteWithAnExistingName RKRoute *route1 = [RKRoute routeWithName:@"test_router" pathPattern:@"/routes" method:RKRequestMethodGET]; [router addRoute:route1]; RKRoute *route2 = [RKRoute routeWithName:@"test_router" pathPattern:@"/routes2" method:RKRequestMethodGET]; - STAssertThrowsSpecificNamed([router addRoute:route2], NSException, NSInternalInconsistencyException, @"Cannot add a route with the same name as an existing route."); + XCTAssertThrowsSpecificNamed([router addRoute:route2], NSException, NSInternalInconsistencyException, @"Cannot add a route with the same name as an existing route."); } - (void)testCanAddARouteWithAnExistingResourcePathPattern @@ -79,7 +79,7 @@ - (void)testCanAddARouteWithAnExistingResourcePathPattern RKRoute *route1 = [RKRoute routeWithName:@"test_router" pathPattern:@"/routes" method:RKRequestMethodGET]; [router addRoute:route1]; RKRoute *route2 = [RKRoute routeWithName:@"test_router2" pathPattern:@"/routes" method:RKRequestMethodGET]; - STAssertNoThrowSpecificNamed([router addRoute:route2], NSException, NSInternalInconsistencyException, @"Cannot add a route with the same path pattern as an existing route."); + XCTAssertNoThrowSpecificNamed([router addRoute:route2], NSException, NSInternalInconsistencyException, @"Cannot add a route with the same path pattern as an existing route."); } - (void)testCannotAddARouteWithAnExistingObjectClassAndMethod @@ -90,9 +90,9 @@ - (void)testCannotAddARouteWithAnExistingObjectClassAndMethod RKRoute *routeWithObjectClassAndDifferentPath = [RKRoute routeWithClass:[RKTestUser class] pathPattern:@"/routes2" method:RKRequestMethodPOST]; [router addRoute:routeWithObjectClassAndMethod]; - STAssertNoThrowSpecificNamed([router addRoute:routeWithObjectClassAndDifferentMethod], NSException, NSInternalInconsistencyException, @"Cannot add a route with the same class and method as an existing route."); + XCTAssertNoThrowSpecificNamed([router addRoute:routeWithObjectClassAndDifferentMethod], NSException, NSInternalInconsistencyException, @"Cannot add a route with the same class and method as an existing route."); - STAssertThrowsSpecificNamed([router addRoute:routeWithObjectClassAndDifferentPath], NSException, NSInternalInconsistencyException, @"Cannot add a route with the same class and method as an existing route."); + XCTAssertThrowsSpecificNamed([router addRoute:routeWithObjectClassAndDifferentPath], NSException, NSInternalInconsistencyException, @"Cannot add a route with the same class and method as an existing route."); } - (void)testCannotAddARouteForAnExistingRelationshipNameAndMethod @@ -103,9 +103,9 @@ - (void)testCannotAddARouteForAnExistingRelationshipNameAndMethod RKRoute *routeWithIdenticalClassAndMethod = [RKRoute routeWithRelationshipName:@"friends" objectClass:[RKTestUser class] pathPattern:@"/friends" method:RKRequestMethodGET]; [router addRoute:routeWithObjectClassAndMethod]; - STAssertNoThrowSpecificNamed([router addRoute:routeWithObjectClassAndDifferentMethod], NSException, NSInternalInconsistencyException, @"Cannot add a relationship route with the same name and class as an existing route."); + XCTAssertNoThrowSpecificNamed([router addRoute:routeWithObjectClassAndDifferentMethod], NSException, NSInternalInconsistencyException, @"Cannot add a relationship route with the same name and class as an existing route."); - STAssertThrowsSpecificNamed([router addRoute:routeWithIdenticalClassAndMethod], NSException, NSInternalInconsistencyException, @"Cannot add a relationship route with the same name and class as an existing route."); + XCTAssertThrowsSpecificNamed([router addRoute:routeWithIdenticalClassAndMethod], NSException, NSInternalInconsistencyException, @"Cannot add a relationship route with the same name and class as an existing route."); } - (void)testCanAddARouteWithAnExistingObjectClassIfMethodIsAny @@ -115,14 +115,14 @@ - (void)testCanAddARouteWithAnExistingObjectClassIfMethodIsAny [router addRoute:route1]; RKRoute *route2 = [RKRoute routeWithClass:[RKTestUser class] pathPattern:@"/routes" method:RKRequestMethodPOST]; - STAssertNoThrowSpecificNamed([router addRoute:route2], NSException, NSInternalInconsistencyException, @"Cannot add a route with the same class and method as an existing route."); + XCTAssertNoThrowSpecificNamed([router addRoute:route2], NSException, NSInternalInconsistencyException, @"Cannot add a route with the same class and method as an existing route."); } - (void)testCannotRemoveARouteThatDoesNotExistInRouter { RKRouteSet *router = [RKRouteSet new]; RKRoute *route = [RKRoute routeWithName:@"fake" pathPattern:@"whatever" method:RKRequestMethodGET]; - STAssertThrowsSpecificNamed([router removeRoute:route], NSException, NSInternalInconsistencyException, @"Cannot remove a route that is not added to the router."); + XCTAssertThrowsSpecificNamed([router removeRoute:route], NSException, NSInternalInconsistencyException, @"Cannot remove a route that is not added to the router."); } - (void)testAllRoutes diff --git a/Tests/Logic/Network/RKRouteTest.m b/Tests/Logic/Network/RKRouteTest.m index ee9dcb9d5f..969a8a1724 100644 --- a/Tests/Logic/Network/RKRouteTest.m +++ b/Tests/Logic/Network/RKRouteTest.m @@ -17,27 +17,27 @@ @implementation RKRouteTest - (void)testCanCreateRouteWithAnExactRequestMethod { - STAssertNoThrowSpecificNamed([RKRoute routeWithName:@"test_router" pathPattern:@"/routes" method:RKRequestMethodGET], NSException, NSInvalidArgumentException, @"Cannot create a route with a bitmask request method value."); + XCTAssertNoThrowSpecificNamed([RKRoute routeWithName:@"test_router" pathPattern:@"/routes" method:RKRequestMethodGET], NSException, NSInvalidArgumentException, @"Cannot create a route with a bitmask request method value."); } - (void)testCannotCreateNamedRouteWithRequestMethodAny { - STAssertThrowsSpecificNamed([RKRoute routeWithName:@"test_router" pathPattern:@"/routes" method:RKRequestMethodAny], NSException, NSInvalidArgumentException, @"Cannot create a route with a bitmask request method value."); + XCTAssertThrowsSpecificNamed([RKRoute routeWithName:@"test_router" pathPattern:@"/routes" method:RKRequestMethodAny], NSException, NSInvalidArgumentException, @"Cannot create a route with a bitmask request method value."); } - (void)testCannotCreateRouteWithNameAndBitmaskRequestMethod { - STAssertThrowsSpecificNamed([RKRoute routeWithName:@"test_router" pathPattern:@"/routes" method:(RKRequestMethodPOST | RKRequestMethodDELETE)], NSException, NSInvalidArgumentException, @"Cannot create a route with a bitmask request method value."); + XCTAssertThrowsSpecificNamed([RKRoute routeWithName:@"test_router" pathPattern:@"/routes" method:(RKRequestMethodPOST | RKRequestMethodDELETE)], NSException, NSInvalidArgumentException, @"Cannot create a route with a bitmask request method value."); } - (void)testCanCreateRouteForAnObjectExistingClassAndBitmaskRequestMethod { - STAssertNoThrowSpecificNamed([RKRoute routeWithClass:[RKTestUser class] pathPattern:@"/routes" method:(RKRequestMethodPOST | RKRequestMethodDELETE)], NSException, NSInvalidArgumentException, @"Cannot create a route with a bitmask request method value."); + XCTAssertNoThrowSpecificNamed([RKRoute routeWithClass:[RKTestUser class] pathPattern:@"/routes" method:(RKRequestMethodPOST | RKRequestMethodDELETE)], NSException, NSInvalidArgumentException, @"Cannot create a route with a bitmask request method value."); } - (void)testCanCreateRouteForAnExistingRelationshipNamendABitmaskRequestMethod { - STAssertNoThrowSpecificNamed([RKRoute routeWithRelationshipName:@"friends" objectClass:[RKTestUser class] pathPattern:@"/friends" method:(RKRequestMethodPOST | RKRequestMethodDELETE)], NSException, NSInvalidArgumentException, @"Cannot create a route with a bitmask request method value."); + XCTAssertNoThrowSpecificNamed([RKRoute routeWithRelationshipName:@"friends" objectClass:[RKTestUser class] pathPattern:@"/friends" method:(RKRequestMethodPOST | RKRequestMethodDELETE)], NSException, NSInvalidArgumentException, @"Cannot create a route with a bitmask request method value."); } @end diff --git a/Tests/Logic/ObjectMapping/RKDynamicMappingTest.m b/Tests/Logic/ObjectMapping/RKDynamicMappingTest.m index 934faee25a..e8d36f496f 100644 --- a/Tests/Logic/ObjectMapping/RKDynamicMappingTest.m +++ b/Tests/Logic/ObjectMapping/RKDynamicMappingTest.m @@ -43,6 +43,25 @@ - (void)testShouldPickTheAppropriateMappingBasedOnAnAttributeValue mapping = [dynamicMapping objectMappingForRepresentation:[RKTestFixture parsedObjectWithContentsOfFixture:@"boy.json"]]; assertThat(mapping, is(notNilValue())); assertThat(NSStringFromClass(mapping.objectClass), is(equalTo(@"Boy"))); + assertThat(dynamicMapping.objectMappings, containsInAnyOrder(girlMapping, boyMapping, nil)); +} + +- (void)testShouldPickTheAppropriateMappingBasedOnAnAttributeValueMap +{ + RKDynamicMapping *dynamicMapping = [RKDynamicMapping new]; + RKObjectMapping *girlMapping = [RKObjectMapping mappingForClass:[Girl class]]; + [girlMapping addAttributeMappingsFromArray:@[@"name"]]; + RKObjectMapping *boyMapping = [RKObjectMapping mappingForClass:[Boy class]]; + [boyMapping addAttributeMappingsFromArray:@[@"name"]]; + NSDictionary *valueMap = @{ @"Girl" : girlMapping, @"Boy" : boyMapping }; + [dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:@"type" expectedValueMap:valueMap]]; + RKObjectMapping *mapping = [dynamicMapping objectMappingForRepresentation:[RKTestFixture parsedObjectWithContentsOfFixture:@"girl.json"]]; + assertThat(mapping, is(notNilValue())); + assertThat(NSStringFromClass(mapping.objectClass), is(equalTo(@"Girl"))); + mapping = [dynamicMapping objectMappingForRepresentation:[RKTestFixture parsedObjectWithContentsOfFixture:@"boy.json"]]; + assertThat(mapping, is(notNilValue())); + assertThat(NSStringFromClass(mapping.objectClass), is(equalTo(@"Boy"))); + assertThat(dynamicMapping.objectMappings, containsInAnyOrder(girlMapping, boyMapping, nil)); } - (void)testShouldPickTheAppropriateMappingBasedOnAnAttributeClass @@ -53,6 +72,7 @@ - (void)testShouldPickTheAppropriateMappingBasedOnAnAttributeClass RKObjectMapping *boyMapping = [RKObjectMapping mappingForClass:[Boy class]]; [boyMapping addAttributeMappingsFromArray:@[@"name"]]; [dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:@"type" expectedClass:[NSString class] objectMapping:girlMapping]]; + assertThat(dynamicMapping.objectMappings, containsInAnyOrder(girlMapping, nil)); RKObjectMapping *mapping = [dynamicMapping objectMappingForRepresentation:[RKTestFixture parsedObjectWithContentsOfFixture:@"girl.json"]]; assertThat(mapping, is(notNilValue())); assertThat(NSStringFromClass(mapping.objectClass), is(equalTo(@"Girl"))); @@ -61,6 +81,7 @@ - (void)testShouldPickTheAppropriateMappingBasedOnAnAttributeClass mapping = [dynamicMapping objectMappingForRepresentation:[RKTestFixture parsedObjectWithContentsOfFixture:@"boy.json"]]; assertThat(mapping, is(notNilValue())); assertThat(NSStringFromClass(mapping.objectClass), is(equalTo(@"Boy"))); + assertThat(dynamicMapping.objectMappings, containsInAnyOrder(boyMapping, nil)); } - (void)testShouldMatchOnAnNSNumberAttributeValue @@ -80,6 +101,23 @@ - (void)testShouldMatchOnAnNSNumberAttributeValue assertThat(NSStringFromClass(mapping.objectClass), is(equalTo(@"Boy"))); } +- (void)testShouldMatchOnAnNSNumberAttributeValueMap +{ + RKDynamicMapping *dynamicMapping = [RKDynamicMapping new]; + RKObjectMapping *girlMapping = [RKObjectMapping mappingForClass:[Girl class]]; + [girlMapping addAttributeMappingsFromArray:@[@"name"]]; + RKObjectMapping *boyMapping = [RKObjectMapping mappingForClass:[Boy class]]; + [boyMapping addAttributeMappingsFromArray:@[@"name"]]; + NSDictionary *valueMap = @{ @0 : girlMapping, @1 : boyMapping }; + [dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:@"numeric_type" expectedValueMap:valueMap]]; + RKObjectMapping *mapping = [dynamicMapping objectMappingForRepresentation:[RKTestFixture parsedObjectWithContentsOfFixture:@"girl.json"]]; + assertThat(mapping, is(notNilValue())); + assertThat(NSStringFromClass(mapping.objectClass), is(equalTo(@"Girl"))); + mapping = [dynamicMapping objectMappingForRepresentation:[RKTestFixture parsedObjectWithContentsOfFixture:@"boy.json"]]; + assertThat(mapping, is(notNilValue())); + assertThat(NSStringFromClass(mapping.objectClass), is(equalTo(@"Boy"))); +} + - (void)testMappingSelectionUsingPredicateMatchers { RKDynamicMapping *dynamicMapping = [RKDynamicMapping new]; @@ -97,6 +135,30 @@ - (void)testMappingSelectionUsingPredicateMatchers assertThat(NSStringFromClass(mapping.objectClass), is(equalTo(@"Boy"))); } +- (void)testMappingSelectionUsingBlockMatchers +{ + RKDynamicMapping *dynamicMapping = [RKDynamicMapping new]; + RKObjectMapping *girlMapping = [RKObjectMapping mappingForClass:[Girl class]]; + [girlMapping addAttributeMappingsFromArray:@[@"name"]]; + RKObjectMapping *boyMapping = [RKObjectMapping mappingForClass:[Boy class]]; + [boyMapping addAttributeMappingsFromArray:@[@"name"]]; + [dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithPossibleMappings:@[boyMapping, girlMapping] block:^RKObjectMapping *(id representation) { + if ([[representation valueForKey:@"type"] isEqualToString:@"Girl"]) { + return girlMapping; + } else if ([[representation valueForKey:@"type"] isEqualToString:@"Boy"]) { + return boyMapping; + } + return nil; + }]]; + RKObjectMapping *mapping = [dynamicMapping objectMappingForRepresentation:[RKTestFixture parsedObjectWithContentsOfFixture:@"girl.json"]]; + assertThat(mapping, is(notNilValue())); + assertThat(NSStringFromClass(mapping.objectClass), is(equalTo(@"Girl"))); + mapping = [dynamicMapping objectMappingForRepresentation:[RKTestFixture parsedObjectWithContentsOfFixture:@"boy.json"]]; + assertThat(mapping, is(notNilValue())); + assertThat(NSStringFromClass(mapping.objectClass), is(equalTo(@"Boy"))); + assertThat(dynamicMapping.objectMappings, containsInAnyOrder(girlMapping, boyMapping, nil)); +} + - (void)testThatRegistrationOfMatcherASecondTimeMovesToTopOfTheStack { RKDynamicMapping *dynamicMapping = [RKDynamicMapping new]; diff --git a/Tests/Logic/ObjectMapping/RKMappingOperationTest.m b/Tests/Logic/ObjectMapping/RKMappingOperationTest.m index 13aed76405..385ad21685 100644 --- a/Tests/Logic/ObjectMapping/RKMappingOperationTest.m +++ b/Tests/Logic/ObjectMapping/RKMappingOperationTest.m @@ -534,9 +534,9 @@ - (void)testThatOneToOneRelationshipOfHasManyDoesNotHaveIncorrectCollectionIndex [userMapping addAttributeMappingsFromDictionary:@{ @"name": @"name", @"@metadata.mapping.collectionIndex": @"luckyNumber" }]; [userMapping addRelationshipMappingWithSourceKeyPath:@"friend" mapping:userMapping]; + NSDictionary *metadata = @{ @"mapping": @{ @"collectionIndex": @25 } }; RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new]; - RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:representation destinationObject:nil mapping:userMapping]; - mappingOperation.metadata = @{ @"mapping": @{ @"collectionIndex": @25 } }; + RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:representation destinationObject:nil mapping:userMapping metadataList:@[metadata]]; mappingOperation.dataSource = dataSource; BOOL success = [mappingOperation performMapping:nil]; expect(success).to.equal(YES); diff --git a/Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m b/Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m index 135e54aebc..42b0eb9732 100644 --- a/Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m +++ b/Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m @@ -239,7 +239,7 @@ - (void)testShouldPerformBasicMapping mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new]; id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"]; RKTestUser *user = [RKTestUser user]; - BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:mapping metadata:nil]; + BOOL success = [mapper mapRepresentation:userInfo toObject:user isNew:YES atKeyPath:@"" usingMapping:mapping metadataList:nil]; assertThatBool(success, is(equalToBool(YES))); assertThatInt([user.userID intValue], is(equalToInt(31337))); assertThat(user.name, is(equalTo(@"Blake Watters"))); @@ -738,6 +738,8 @@ - (void)testAccessingPropertyAndRelationshipSpecificMappingInfo NSDictionary *representation = @{ @"name": @"Blake Watters", @"friends": @[ @{ @"name": @"Jeff Arena"} ] }; RKTestUser *user = [RKTestUser new]; RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new]; + OCMockObject *mockDataSource = [OCMockObject partialMockForObject:dataSource]; + [[[mockDataSource stub] andReturnValue:@YES] mappingOperationShouldCollectMappingInfo:OCMOCK_ANY]; RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:representation destinationObject:user mapping:mapping]; mappingOperation.dataSource = dataSource; [mappingOperation start]; @@ -760,6 +762,8 @@ - (void)testThatMappingHasManyDoesNotDuplicateRelationshipMappingInMappingInfo NSDictionary *representation = @{ @"name": @"Blake Watters", @"friends": @[ @{ @"name": @"Jeff Arena"}, @{ @"name": @"Dan Gellert" } ] }; RKTestUser *user = [RKTestUser new]; RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new]; + OCMockObject *mockDataSource = [OCMockObject partialMockForObject:dataSource]; + [[[mockDataSource stub] andReturnValue:@YES] mappingOperationShouldCollectMappingInfo:OCMOCK_ANY]; RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:representation destinationObject:user mapping:mapping]; mappingOperation.dataSource = dataSource; [mappingOperation start]; @@ -781,6 +785,8 @@ - (void)testMapperOperationAggregatesMappingInfoFromChildOperations NSDictionary *representation = @{ @"name": @"Blake Watters", @"friends": @[ @{ @"name": @"Jeff Arena"}, @{ @"name": @"Dan Gellert" } ] }; RKTestUser *user = [RKTestUser new]; RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new]; + OCMockObject *mockDataSource = [OCMockObject partialMockForObject:dataSource]; + [[[mockDataSource stub] andReturnValue:@YES] mappingOperationShouldCollectMappingInfo:OCMOCK_ANY]; RKMapperOperation *mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:representation mappingsDictionary:@{ [NSNull null]: mapping }]; mapperOperation.targetObject = user; mapperOperation.mappingOperationDataSource = dataSource; @@ -799,7 +805,7 @@ - (BOOL)fakeValidateValue:(inout id *)ioValue forKeyPath:(NSString *)inKey error - (void)testShouldNotifyTheDelegateWhenItFailedToMapAnObject { id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKMapperOperationDelegate)]; - RKObjectMapping *mapping = [RKObjectMapping mappingForClass:NSClassFromString(@"OCPartialMockObject")]; + RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]]; [mapping addAttributeMappingsFromArray:@[@"name"]]; id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"]; @@ -813,9 +819,9 @@ - (void)testShouldNotifyTheDelegateWhenItFailedToMapAnObject [[mockDelegate expect] mapper:mapper didFailMappingOperation:OCMOCK_ANY forKeyPath:nil withError:OCMOCK_ANY]; mapper.delegate = mockDelegate; [mapper start]; + [mockInspector stopMocking]; [mockObject verify]; [mockDelegate verify]; - [mockInspector stopMocking]; } #pragma mark - RKObjectMappingOperationTests @@ -1339,7 +1345,7 @@ - (void)testDelegateIsInformedWhenANilValueIsMappedForNSNullWithExistingValue operation.delegate = mockDelegate; NSError *error = nil; [[mockDelegate expect] mappingOperation:operation didFindValue:[NSNull null] forKeyPath:@"name" mapping:nameMapping]; - [[[mockDelegate stub] andReturnValue:OCMOCK_VALUE((BOOL){YES})] mappingOperation:operation shouldSetValue:nil forKeyPath:@"name" usingMapping:nameMapping]; + [[[mockDelegate stub] andReturnValue:OCMOCK_VALUE(YES)] mappingOperation:operation shouldSetValue:nil forKeyPath:@"name" usingMapping:nameMapping]; [[mockDelegate expect] mappingOperation:operation didSetValue:nil forKeyPath:@"name" usingMapping:nameMapping]; operation.dataSource = dataSource; [operation performMapping:&error]; @@ -1385,8 +1391,7 @@ - (void)testShouldOptionallySetDefaultValueForAMissingKeyPath RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new]; operation.dataSource = dataSource; id mockMapping = [OCMockObject partialMockForObject:mapping]; - BOOL returnValue = YES; - [[[mockMapping expect] andReturnValue:OCMOCK_VALUE(returnValue)] assignsDefaultValueForMissingAttributes]; + [[[mockMapping expect] andReturnValue:@YES] assignsDefaultValueForMissingAttributes]; NSError *error = nil; [operation performMapping:&error]; [mockUser verify]; @@ -1408,8 +1413,7 @@ - (void)testMappingFromMissingSourceKeyPathAssignsNil RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new]; operation.dataSource = dataSource; id mockMapping = [OCMockObject partialMockForObject:mapping]; - BOOL returnValue = NO; - [[[mockMapping expect] andReturnValue:OCMOCK_VALUE(returnValue)] shouldSetDefaultValueForMissingAttributes]; + [[[mockMapping expect] andReturnValue:@NO] shouldSetDefaultValueForMissingAttributes]; NSError *error = nil; [operation performMapping:&error]; assertThat(user.name, is(equalTo(@"Blake Watters"))); @@ -1606,7 +1610,7 @@ - (void)testShouldMapANestedObject mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new]; id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"]; RKTestUser *user = [RKTestUser user]; - BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil]; + BOOL success = [mapper mapRepresentation:userInfo toObject:user isNew:YES atKeyPath:@"" usingMapping:userMapping metadataList:nil]; assertThatBool(success, is(equalToBool(YES))); assertThat(user.name, is(equalTo(@"Blake Watters"))); assertThat(user.address, isNot(nilValue())); @@ -1628,7 +1632,7 @@ - (void)testShouldMapANestedObjectToCollection mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new]; id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"]; RKTestUser *user = [RKTestUser user]; - BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil]; + BOOL success = [mapper mapRepresentation:userInfo toObject:user isNew:YES atKeyPath:@"" usingMapping:userMapping metadataList:nil]; assertThatBool(success, is(equalToBool(YES))); assertThat(user.name, is(equalTo(@"Blake Watters"))); assertThat(user.friends, isNot(nilValue())); @@ -1651,7 +1655,7 @@ - (void)testShouldMapANestedObjectToOrderedSetCollection mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new]; id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"]; RKTestUser *user = [RKTestUser user]; - BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil]; + BOOL success = [mapper mapRepresentation:userInfo toObject:user isNew:YES atKeyPath:@"" usingMapping:userMapping metadataList:nil]; assertThatBool(success, is(equalToBool(YES))); assertThat(user.name, is(equalTo(@"Blake Watters"))); assertThat(user.friendsOrderedSet, isNot(nilValue())); @@ -1671,7 +1675,7 @@ - (void)testShouldMapANestedObjectCollection mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new]; id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"]; RKTestUser *user = [RKTestUser user]; - BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil]; + BOOL success = [mapper mapRepresentation:userInfo toObject:user isNew:YES atKeyPath:@"" usingMapping:userMapping metadataList:nil]; assertThatBool(success, is(equalToBool(YES))); assertThat(user.name, is(equalTo(@"Blake Watters"))); assertThat(user.friends, isNot(nilValue())); @@ -1693,7 +1697,7 @@ - (void)testShouldMapANestedArrayIntoASet mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new]; id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"]; RKTestUser *user = [RKTestUser user]; - BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil]; + BOOL success = [mapper mapRepresentation:userInfo toObject:user isNew:YES atKeyPath:@"" usingMapping:userMapping metadataList:nil]; assertThatBool(success, is(equalToBool(YES))); assertThat(user.name, is(equalTo(@"Blake Watters"))); assertThat(user.friendsSet, isNot(nilValue())); @@ -1716,7 +1720,7 @@ - (void)testShouldMapANestedArrayIntoAnOrderedSet mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new]; id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"]; RKTestUser *user = [RKTestUser user]; - BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil]; + BOOL success = [mapper mapRepresentation:userInfo toObject:user isNew:YES atKeyPath:@"" usingMapping:userMapping metadataList:nil]; assertThatBool(success, is(equalToBool(YES))); assertThat(user.name, is(equalTo(@"Blake Watters"))); assertThat(user.friendsOrderedSet, isNot(nilValue())); @@ -1748,7 +1752,7 @@ - (void)testShouldNotSetThePropertyWhenTheNestedObjectIsIdentical RKMapperOperation *mapper = [RKMapperOperation new]; mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new]; id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"]; - [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil]; + [mapper mapRepresentation:userInfo toObject:user isNew:NO atKeyPath:@"" usingMapping:userMapping metadataList:nil]; } - (void)testSkippingOfIdenticalObjectsInformsDelegate @@ -1775,7 +1779,7 @@ - (void)testSkippingOfIdenticalObjectsInformsDelegate RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new]; operation.dataSource = dataSource; id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKMappingOperationDelegate)]; - [[[mockDelegate stub] andReturnValue:OCMOCK_VALUE((BOOL){NO})] mappingOperation:operation shouldSetValue:OCMOCK_ANY forKeyPath:OCMOCK_ANY usingMapping:OCMOCK_ANY]; + [[[mockDelegate stub] andReturnValue:OCMOCK_VALUE(NO)] mappingOperation:operation shouldSetValue:OCMOCK_ANY forKeyPath:OCMOCK_ANY usingMapping:OCMOCK_ANY]; [[mockDelegate expect] mappingOperation:operation didNotSetUnchangedValue:OCMOCK_ANY forKeyPath:@"address" usingMapping:hasOneMapping]; operation.delegate = mockDelegate; [operation performMapping:nil]; @@ -1809,7 +1813,7 @@ - (void)testShouldNotSetThePropertyWhenTheNestedObjectCollectionIsIdentical id mockUser = [OCMockObject partialMockForObject:user]; [[mockUser reject] setFriends:OCMOCK_ANY]; - [mapper mapRepresentation:userInfo toObject:mockUser atKeyPath:@"" usingMapping:userMapping metadata:nil]; + [mapper mapRepresentation:userInfo toObject:mockUser isNew:NO atKeyPath:@"" usingMapping:userMapping metadataList:nil]; [mockUser verify]; } @@ -1834,8 +1838,7 @@ - (void)testShouldOptionallyNilOutTheRelationshipIfItIsMissing NSMutableDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy]; [dictionary removeObjectForKey:@"address"]; id mockMapping = [OCMockObject partialMockForObject:userMapping]; - BOOL returnValue = YES; - [[[mockMapping expect] andReturnValue:OCMOCK_VALUE(returnValue)] assignsNilForMissingRelationships]; + [[[mockMapping expect] andReturnValue:@YES] assignsNilForMissingRelationships]; RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:mockUser mapping:mockMapping]; RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new]; operation.dataSource = dataSource; @@ -1864,8 +1867,7 @@ - (void)testShouldNotNilOutTheRelationshipIfItIsMissingAndCurrentlyNilOnTheTarge NSMutableDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy]; [dictionary removeObjectForKey:@"address"]; id mockMapping = [OCMockObject partialMockForObject:userMapping]; - BOOL returnValue = YES; - [[[mockMapping expect] andReturnValue:OCMOCK_VALUE(returnValue)] setNilForMissingRelationships]; + [[[mockMapping expect] andReturnValue:@YES] setNilForMissingRelationships]; RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:mockUser mapping:mockMapping]; RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new]; operation.dataSource = dataSource; @@ -2671,8 +2673,7 @@ - (void)testMappingURLFromMetadata RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]]; [userMapping addAttributeMappingsFromDictionary:@{ @"name": @"name", @"@metadata.HTTP.request.URL": @"website" }]; RKTestUser *user = [RKTestUser new]; - RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:objectRepresentation destinationObject:user mapping:userMapping]; - mappingOperation.metadata = metadata; + RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:objectRepresentation destinationObject:user mapping:userMapping metadataList:@[metadata]]; RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new]; mappingOperation.dataSource = dataSource; [mappingOperation start]; @@ -2688,8 +2689,7 @@ - (void)testMappingHeadersFromMetadata RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]]; [userMapping addAttributeMappingsFromDictionary:@{ @"name": @"name", @"@metadata.HTTP.request.headers.Content-Type": @"emailAddress" }]; RKTestUser *user = [RKTestUser new]; - RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:objectRepresentation destinationObject:user mapping:userMapping]; - mappingOperation.metadata = metadata; + RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:objectRepresentation destinationObject:user mapping:userMapping metadataList:@[metadata]]; RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new]; mappingOperation.dataSource = dataSource; [mappingOperation start]; diff --git a/Tests/Logic/ObjectMapping/RKPaginatorTest.m b/Tests/Logic/ObjectMapping/RKPaginatorTest.m index dc9961aea4..50235b685c 100644 --- a/Tests/Logic/ObjectMapping/RKPaginatorTest.m +++ b/Tests/Logic/ObjectMapping/RKPaginatorTest.m @@ -137,8 +137,7 @@ - (void)testThatResourcePathPatternEvaluatesAgainstPaginator NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; - NSUInteger currentPage = 1; - [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; + [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE((NSUInteger)1)] currentPage]; expect([paginator.URL relativeString]).to.equal(@"/paginate?per_page=25&page=1"); } @@ -147,8 +146,7 @@ - (void)testThatURLReturnedReflectsStateOfPaginator NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; - NSUInteger currentPage = 1; - [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; + [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE((NSUInteger)1)] currentPage]; expect([[mockPaginator URL] query]).to.equal(@"per_page=25&page=1"); } @@ -157,8 +155,7 @@ - (void)testThatURLReturnedReflectsStateOfPaginatorWithOffset NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationOffsetURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; - NSUInteger currentPage = 1; - [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; + [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE((NSUInteger)1)] currentPage]; expect([[mockPaginator URL] query]).to.equal(@"limit=25&offset=0"); } @@ -323,21 +320,15 @@ - (void)testKnowledgeOfHasANextPage { RKPaginator *paginator = [RKPaginator new]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; - BOOL isLoaded = YES; - NSUInteger perPage = 5; - NSUInteger pageCount = 3; - [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(isLoaded)] isLoaded]; - [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(perPage)] perPage]; - [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(pageCount)] pageCount]; - - NSUInteger currentPage = 1; - [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; + [[[mockPaginator stub] andReturnValue:@YES] isLoaded]; + [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE((NSUInteger)5)] perPage]; + [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE((NSUInteger)3)] pageCount]; + + [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE((NSUInteger)1)] currentPage]; assertThatBool([mockPaginator hasNextPage], is(equalToBool(YES))); - currentPage = 2; - [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; + [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE((NSUInteger)2)] currentPage]; assertThatBool([mockPaginator hasNextPage], is(equalToBool(YES))); - currentPage = 3; - [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; + [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE((NSUInteger)3)] currentPage]; assertThatBool([mockPaginator hasNextPage], is(equalToBool(NO))); } @@ -345,50 +336,40 @@ - (void)testHasNextPageRaisesExpectionWhenNotLoaded { RKPaginator *paginator = [RKPaginator new]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; - BOOL loaded = NO; - [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(loaded)] isLoaded]; - STAssertThrows([mockPaginator hasNextPage], @"Expected exception due to isLoaded == NO"); + [[[mockPaginator stub] andReturnValue:@NO] isLoaded]; + XCTAssertThrows([mockPaginator hasNextPage], @"Expected exception due to isLoaded == NO"); } - (void)testHasNextPageRaisesExpectionWhenPageCountIsUnknown { RKPaginator *paginator = [RKPaginator new]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; - BOOL loaded = YES; - [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(loaded)] isLoaded]; - BOOL hasPageCount = NO; - [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(hasPageCount)] hasPageCount]; - STAssertThrows([mockPaginator hasNextPage], @"Expected exception due to pageCount == NSUIntegerMax"); + [[[mockPaginator stub] andReturnValue:@YES] isLoaded]; + [[[mockPaginator stub] andReturnValue:@NO] hasPageCount]; + XCTAssertThrows([mockPaginator hasNextPage], @"Expected exception due to pageCount == NSUIntegerMax"); } - (void)testHasPreviousPageRaisesExpectionWhenNotLoaded { RKPaginator *paginator = [RKPaginator new]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; - BOOL loaded = NO; - [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(loaded)] isLoaded]; - STAssertThrows([mockPaginator hasPreviousPage], @"Expected exception due to isLoaded == NO"); + [[[mockPaginator stub] andReturnValue:@NO] isLoaded]; + XCTAssertThrows([mockPaginator hasPreviousPage], @"Expected exception due to isLoaded == NO"); } - (void)testKnowledgeOfPreviousPage { RKPaginator *paginator = [RKPaginator new]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; - BOOL isLoaded = YES; - NSUInteger perPage = 5; - NSUInteger pageCount = 3; - [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(isLoaded)] isLoaded]; - [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(perPage)] perPage]; - [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(pageCount)] pageCount]; - - NSUInteger currentPage = 3; - [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; + [[[mockPaginator stub] andReturnValue:@YES] isLoaded]; + [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE((NSUInteger)5)] perPage]; + [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE((NSUInteger)3)] pageCount]; + + [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE((NSUInteger)3)] currentPage]; assertThatBool([mockPaginator hasPreviousPage], is(equalToBool(YES))); - currentPage = 2; - [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; + [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE((NSUInteger)2)] currentPage]; assertThatBool([mockPaginator hasPreviousPage], is(equalToBool(YES))); - currentPage = 1; - [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; + [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE((NSUInteger)1)] currentPage]; assertThatBool([mockPaginator hasPreviousPage], is(equalToBool(NO))); } diff --git a/Tests/Logic/Search/RKSearchIndexerTest.m b/Tests/Logic/Search/RKSearchIndexerTest.m index 4f9e552fe2..271fb7cbce 100644 --- a/Tests/Logic/Search/RKSearchIndexerTest.m +++ b/Tests/Logic/Search/RKSearchIndexerTest.m @@ -396,11 +396,9 @@ - (void)testThatDelegateCanDenyCreationOfSearchWordForWord [human setValue:@"This is my name" forKey:@"name"]; id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKSearchIndexerDelegate)]; - BOOL returnValue = NO; - [[[mockDelegate expect] andReturnValue:OCMOCK_VALUE(returnValue)] searchIndexer:OCMOCK_ANY shouldInsertSearchWordForWord:@"this" inManagedObjectContext:managedObjectContext]; - returnValue = YES; - [[[mockDelegate expect] andReturnValue:OCMOCK_VALUE(returnValue)] searchIndexer:OCMOCK_ANY shouldInsertSearchWordForWord:@"my" inManagedObjectContext:managedObjectContext]; - [[[mockDelegate expect] andReturnValue:OCMOCK_VALUE(returnValue)] searchIndexer:OCMOCK_ANY shouldInsertSearchWordForWord:@"name" inManagedObjectContext:managedObjectContext]; + [[[mockDelegate expect] andReturnValue:@NO] searchIndexer:OCMOCK_ANY shouldInsertSearchWordForWord:@"this" inManagedObjectContext:managedObjectContext]; + [[[mockDelegate expect] andReturnValue:@YES] searchIndexer:OCMOCK_ANY shouldInsertSearchWordForWord:@"my" inManagedObjectContext:managedObjectContext]; + [[[mockDelegate expect] andReturnValue:@YES] searchIndexer:OCMOCK_ANY shouldInsertSearchWordForWord:@"name" inManagedObjectContext:managedObjectContext]; indexer.delegate = mockDelegate; NSUInteger count = [indexer indexManagedObject:human]; @@ -429,8 +427,7 @@ - (void)testThatDelegateIsInformedWhenSearchWordIsCreated [human setValue:@"This is my name" forKey:@"name"]; id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKSearchIndexerDelegate)]; - BOOL returnValue = YES; - [[[mockDelegate stub] andReturnValue:OCMOCK_VALUE(returnValue)] searchIndexer:indexer shouldInsertSearchWordForWord:OCMOCK_ANY inManagedObjectContext:OCMOCK_ANY]; + [[[mockDelegate stub] andReturnValue:@YES] searchIndexer:indexer shouldInsertSearchWordForWord:OCMOCK_ANY inManagedObjectContext:OCMOCK_ANY]; [[mockDelegate expect] searchIndexer:indexer didInsertSearchWord:OCMOCK_ANY forWord:@"this" inManagedObjectContext:managedObjectContext]; [[mockDelegate expect] searchIndexer:indexer didInsertSearchWord:OCMOCK_ANY forWord:@"is" inManagedObjectContext:managedObjectContext]; [[mockDelegate expect] searchIndexer:indexer didInsertSearchWord:OCMOCK_ANY forWord:@"my" inManagedObjectContext:managedObjectContext]; @@ -458,13 +455,12 @@ - (void)testThatDelegateCanBeUsedToFetchExistingSearchWords [human setValue:@"This is my name" forKey:@"name"]; id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKSearchIndexerDelegate)]; - BOOL returnValue = NO; RKSearchWord *searchWord = [NSEntityDescription insertNewObjectForEntityForName:@"RKSearchWord" inManagedObjectContext:managedObjectContext]; [[[mockDelegate expect] andReturn:searchWord] searchIndexer:indexer searchWordForWord:@"this" inManagedObjectContext:managedObjectContext error:(NSError * __autoreleasing *)[OCMArg anyPointer]]; [[[mockDelegate expect] andReturn:searchWord] searchIndexer:indexer searchWordForWord:@"is" inManagedObjectContext:managedObjectContext error:(NSError * __autoreleasing *)[OCMArg anyPointer]]; [[[mockDelegate expect] andReturn:searchWord] searchIndexer:indexer searchWordForWord:@"my" inManagedObjectContext:managedObjectContext error:(NSError * __autoreleasing *)[OCMArg anyPointer]]; [[[mockDelegate expect] andReturn:searchWord] searchIndexer:indexer searchWordForWord:@"name" inManagedObjectContext:managedObjectContext error:(NSError * __autoreleasing *)[OCMArg anyPointer]]; - [[[mockDelegate stub] andReturnValue:OCMOCK_VALUE(returnValue)] searchIndexer:indexer shouldInsertSearchWordForWord:OCMOCK_ANY inManagedObjectContext:OCMOCK_ANY]; + [[[mockDelegate stub] andReturnValue:@NO] searchIndexer:indexer shouldInsertSearchWordForWord:OCMOCK_ANY inManagedObjectContext:OCMOCK_ANY]; [[mockDelegate reject] searchIndexer:indexer didInsertSearchWord:OCMOCK_ANY forWord:OCMOCK_ANY inManagedObjectContext:OCMOCK_ANY]; indexer.delegate = mockDelegate; @@ -489,8 +485,7 @@ - (void)testThatTheDelegateCanDeclineIndexingOfAnObject [human setValue:@"This is my name" forKey:@"name"]; id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKSearchIndexerDelegate)]; - BOOL returnValue = NO; - [[[mockDelegate expect] andReturnValue:OCMOCK_VALUE(returnValue)] searchIndexer:indexer shouldIndexManagedObject:human]; + [[[mockDelegate expect] andReturnValue:@NO] searchIndexer:indexer shouldIndexManagedObject:human]; indexer.delegate = mockDelegate; [indexer indexChangedObjectsInManagedObjectContext:managedObjectContext waitUntilFinished:YES]; diff --git a/Tests/Logic/Testing/RKMappingTestTest.m b/Tests/Logic/Testing/RKMappingTestTest.m index 67ea385162..a4ca87ef05 100644 --- a/Tests/Logic/Testing/RKMappingTestTest.m +++ b/Tests/Logic/Testing/RKMappingTestTest.m @@ -12,7 +12,7 @@ #import "RKHuman.h" #import "RKCat.h" -@interface RKMappingTestTest : SenTestCase +@interface RKMappingTestTest : XCTestCase @property (nonatomic, strong) id objectRepresentation; @property (nonatomic, strong) RKMappingTest *mappingTest; @end diff --git a/Tests/RKTestEnvironment.h b/Tests/RKTestEnvironment.h index 69590debb1..4e8bb4567a 100644 --- a/Tests/RKTestEnvironment.h +++ b/Tests/RKTestEnvironment.h @@ -18,7 +18,7 @@ // limitations under the License. // -#import +#import #import #import @@ -37,6 +37,6 @@ /* Base class for RestKit test cases. Provides initialization of testing infrastructure. */ -@interface RKTestCase : SenTestCase +@interface RKTestCase : XCTestCase @end diff --git a/Vendor/LibComponentLogging/NSLog/LCLNSLog_RK.h b/Vendor/LibComponentLogging/NSLog/LCLNSLog_RK.h deleted file mode 100644 index 06f9bda20d..0000000000 --- a/Vendor/LibComponentLogging/NSLog/LCLNSLog_RK.h +++ /dev/null @@ -1,100 +0,0 @@ -// -// -// LCLNSLog_RK.h -// -// -// Copyright (c) 2008-2011 Arne Harren -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - - -// -// RKLCLNSLog -// -// This file provides a simple LibComponentLogging logger implementation which -// redirects logging to NSLog. -// -// The logger uses the following format -// -// ::: -// -// where is -// -//