Added support for polymorphic object mapping (Github #105, #244). This enables you to dynamically map objects to different destination classes or using different mapping strategies via configuration or callbacks. See Docs/Object Mapping.md for details.

Other changes include:
* Eliminated the RKObjectFactory protocol and implementations. Object mapping instances themselves are
now responsible for instantiating target objects for mapping.
* Introduced RKObjectAbstractMapping superclass for RKObjectMapping and RKObjectPolymorphicMapping.
* Updated example applications to use block object loaders (RKTwitter and RKTwitterCoreData)
* Refactored method signatures of RKObjectMapper, RKObjectMapping, and RKObjectMappingProvider to reflect the
existence of abstract mapping types. This was necessary to make polymorphic mappings integrate cleanly.
* Fixed overlap in RestKit error domains between network and object mapping. fixes #208
This commit is contained in:
Blake Watters
2011-07-30 16:00:36 -04:00
parent 072e1ee58b
commit 670234b775
64 changed files with 1411 additions and 601 deletions

View File

@@ -13,5 +13,4 @@
#import "RKObjectMappingProvider.h"
#import "RKObjectMappingResult.h"
#import "RKObjectMapper.h"
#import "RKObjectFactory.h"
#import "RKParserRegistry.h"

View File

@@ -0,0 +1,19 @@
//
// RKObjectAbstractMapping.h
// RestKit
//
// Created by Blake Watters on 7/29/11.
// Copyright 2011 RestKit. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
An abstract superclass for RKObjectMapping and RKObjectPolymorphic mapping.
Provides type safety checks
*/
@interface RKObjectAbstractMapping : NSObject
- (BOOL)forceCollectionMapping;
- (Class)objectClass;
@end

View File

@@ -0,0 +1,25 @@
//
// RKObjectAbstractMapping.m
// RestKit
//
// Created by Blake Watters on 7/29/11.
// Copyright 2011 RestKit. All rights reserved.
//
#import "RKObjectAbstractMapping.h"
@implementation RKObjectAbstractMapping
- (BOOL)forceCollectionMapping {
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
userInfo:nil];
}
- (Class)objectClass {
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
userInfo:nil];
}
@end

View File

@@ -1,23 +0,0 @@
//
// RKObjectFactory.h
// RestKit
//
// Created by Blake Watters on 5/10/11.
// Copyright 2011 Two Toasters. All rights reserved.
//
#import "RKObjectMapping.h"
/**
Defines a protocol for the creation of objects during an
object mapping operation. Used to initialize objects that
are going to be subsequently mapped with an object mapping
*/
@protocol RKObjectFactory <NSObject>
/**
Return a new initialized, auto-released object with the specified object mapping.
*/
- (id)objectWithMapping:(RKObjectMapping*)objectMapping andData:(id)mappableData;
@end

View File

@@ -127,16 +127,6 @@
#pragma mark - Subclass Hooks
/**
Overloaded by RKManagedObjectLoader to provide support for creation
and find/update of managed object instances
@protected
*/
- (id<RKObjectFactory>)createObjectFactory {
return nil;
}
/**
Overloaded by RKManagedObjectLoader to serialize/deserialize managed objects
at thread boundaries.
@@ -179,7 +169,6 @@
}
RKObjectMapper* mapper = [RKObjectMapper mapperWithObject:parsedData mappingProvider:mappingProvider];
mapper.objectFactory = [self createObjectFactory];
mapper.targetObject = targetObject;
mapper.delegate = self;
RKObjectMappingResult* result = [mapper performMapping];
@@ -208,7 +197,7 @@
NSString* rootKeyPath = self.objectMapping.rootKeyPath ? self.objectMapping.rootKeyPath : @"";
RKLogDebug(@"Found directly configured object mapping, creating temporary mapping provider %@", (rootKeyPath ? @"for keyPath '%@'" : nil));
mappingProvider = [[RKObjectMappingProvider new] autorelease];
[mappingProvider setObjectMapping:self.objectMapping forKeyPath:rootKeyPath];
[mappingProvider setMapping:self.objectMapping forKeyPath:rootKeyPath];
} else {
RKLogDebug(@"No object mapping provider, using mapping provider from parent object manager to perform KVC mapping");
mappingProvider = self.objectManager.mappingProvider;

View File

@@ -281,6 +281,7 @@ typedef enum {
POST a remote object instance and yield the object loader to the block before sending
@see sendObject:method:delegate:block
- (RKObjectLoader*)postObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block;
*/
- (RKObjectLoader*)postObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block;

View File

@@ -47,8 +47,8 @@ static RKObjectManager* sharedManager = nil;
// Setup default error message mappings
RKObjectMapping* errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping mapKeyPath:@"" toAttribute:@"errorMessage"];
[_mappingProvider setObjectMapping:errorMapping forKeyPath:@"error"];
[_mappingProvider setObjectMapping:errorMapping forKeyPath:@"errors"];
[_mappingProvider setMapping:errorMapping forKeyPath:@"error"];
[_mappingProvider setMapping:errorMapping forKeyPath:@"errors"];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reachabilityChanged:)

View File

@@ -11,7 +11,6 @@
#import "RKObjectMappingOperation.h"
#import "RKObjectMappingResult.h"
#import "RKObjectMappingProvider.h"
#import "RKObjectFactory.h"
#import "../Support/Support.h"
/**
@@ -27,12 +26,12 @@
- (void)objectMapperWillBeginMapping:(RKObjectMapper*)objectMapper;
- (void)objectMapperDidFinishMapping:(RKObjectMapper*)objectMapper;
- (void)objectMapper:(RKObjectMapper*)objectMapper didAddError:(NSError*)error;
- (void)objectMapper:(RKObjectMapper*)objectMapper didFindMappableObject:(id)object atKeyPath:(NSString*)keyPath withMapping:(RKObjectMapping*)mapping;
- (void)objectMapper:(RKObjectMapper*)objectMapper didFindMappableObject:(id)object atKeyPath:(NSString*)keyPath withMapping:(RKObjectAbstractMapping*)mapping;
- (void)objectMapper:(RKObjectMapper*)objectMapper didNotFindMappableObjectAtKeyPath:(NSString*)keyPath;
- (void)objectMapper:(RKObjectMapper*)objectMapper willMapFromObject:(id)sourceObject toObject:(id)destinationObject atKeyPath:(NSString*)keyPath usingMapping:(RKObjectMapping*)objectMapping;
- (void)objectMapper:(RKObjectMapper*)objectMapper didMapFromObject:(id)sourceObject toObject:(id)destinationObject atKeyPath:(NSString*)keyPath usingMapping:(RKObjectMapping*)objectMapping;
- (void)objectMapper:(RKObjectMapper*)objectMapper didFailMappingFromObject:(id)sourceObject toObject:(id)destinationObject withError:(NSError*)error atKeyPath:(NSString*)keyPath usingMapping:(RKObjectMapping*)objectMapping;
- (void)objectMapper:(RKObjectMapper*)objectMapper willMapFromObject:(id)sourceObject toObject:(id)destinationObject atKeyPath:(NSString*)keyPath usingMapping:(RKObjectAbstractMapping*)objectMapping;
- (void)objectMapper:(RKObjectMapper*)objectMapper didMapFromObject:(id)sourceObject toObject:(id)destinationObject atKeyPath:(NSString*)keyPath usingMapping:(RKObjectAbstractMapping*)objectMapping;
- (void)objectMapper:(RKObjectMapper*)objectMapper didFailMappingFromObject:(id)sourceObject toObject:(id)destinationObject withError:(NSError*)error atKeyPath:(NSString*)keyPath usingMapping:(RKObjectAbstractMapping*)objectMapping;
@end
@interface RKObjectMapper : NSObject {
@@ -40,7 +39,6 @@
id _targetObject;
RKObjectMappingProvider* _mappingProvider;
id<RKObjectMapperDelegate> _delegate;
id<RKObjectFactory> _objectFactory;
NSMutableArray* _errors;
}
@@ -48,7 +46,6 @@
@property (nonatomic, assign) id targetObject;
@property (nonatomic, readonly) RKObjectMappingProvider* mappingProvider;
@property (nonatomic, assign) id<RKObjectMapperDelegate> delegate;
@property (nonatomic, assign) id<RKObjectFactory> objectFactory;
@property (nonatomic, readonly) NSArray* errors;
+ (id)mapperWithObject:(id)object mappingProvider:(RKObjectMappingProvider*)mappingProvider;

View File

@@ -20,7 +20,6 @@
@synthesize targetObject = _targetObject;
@synthesize delegate =_delegate;
@synthesize mappingProvider = _mappingProvider;
@synthesize objectFactory = _objectFactory;
@synthesize errors = _errors;
+ (id)mapperWithObject:(id)object mappingProvider:(RKObjectMappingProvider*)mappingProvider {
@@ -98,7 +97,7 @@
#pragma mark - Mapping Primitives
- (id)mapObject:(id)mappableObject atKeyPath:(NSString*)keyPath usingMapping:(RKObjectMapping*)objectMapping {
- (id)mapObject:(id)mappableObject atKeyPath:(NSString*)keyPath usingMapping:(RKObjectAbstractMapping*)objectMapping {
NSAssert([mappableObject respondsToSelector:@selector(setValue:forKeyPath:)], @"Expected self.object to be KVC compliant");
id destinationObject = nil;
@@ -130,7 +129,7 @@
return nil;
}
- (NSArray*)mapCollection:(NSArray*)mappableObjects atKeyPath:(NSString*)keyPath usingMapping:(RKObjectMapping*)mapping {
- (NSArray*)mapCollection:(NSArray*)mappableObjects atKeyPath:(NSString*)keyPath usingMapping:(RKObjectAbstractMapping*)mapping {
NSAssert(mappableObjects != nil, @"Cannot map without an collection of mappable objects");
NSAssert(mapping != nil, @"Cannot map without a mapping to consult");
@@ -158,8 +157,13 @@
[self addErrorWithCode:RKObjectMapperErrorObjectMappingTypeMismatch message:errorMessage keyPath:keyPath userInfo:nil];
return nil;
}
for (id mappableObject in objectsToMap) {
id destinationObject = [self objectWithMapping:mapping andData:mappableObject];
if (! destinationObject) {
continue;
}
BOOL success = [self mapFromObject:mappableObject toObject:destinationObject atKeyPath:keyPath usingMapping:mapping];
if (success) {
[mappedObjects addObject:destinationObject];
@@ -170,7 +174,7 @@
}
// The workhorse of this entire process. Emits object loading operations
- (BOOL)mapFromObject:(id)mappableObject toObject:(id)destinationObject atKeyPath:keyPath usingMapping:(RKObjectMapping*)mapping {
- (BOOL)mapFromObject:(id)mappableObject toObject:(id)destinationObject atKeyPath:keyPath usingMapping:(RKObjectAbstractMapping*)mapping {
NSAssert(destinationObject != nil, @"Cannot map without a target object to assign the results to");
NSAssert(mappableObject != nil, @"Cannot map without a collection of attributes");
NSAssert(mapping != nil, @"Cannot map without an mapping");
@@ -180,9 +184,11 @@
[self.delegate objectMapper:self willMapFromObject:mappableObject toObject:destinationObject atKeyPath:keyPath usingMapping:mapping];
}
NSError* error = nil;
RKObjectMappingOperation* operation = [RKObjectMappingOperation mappingOperationFromObject:mappableObject toObject:destinationObject withObjectMapping:mapping];
operation.objectFactory = self;
NSError* error = nil;
RKObjectMappingOperation* operation = [RKObjectMappingOperation mappingOperationFromObject:mappableObject
toObject:destinationObject
withMapping:mapping];
BOOL success = [operation performMapping:&error];
if (success) {
if ([self.delegate respondsToSelector:@selector(objectMapper:didMapFromObject:toObject:atKeyPath:usingMapping:)]) {
@@ -212,7 +218,7 @@
// Perform the mapping
BOOL foundMappable = NO;
NSMutableDictionary* results = [NSMutableDictionary dictionary];
NSDictionary* keyPathsAndObjectMappings = [self.mappingProvider objectMappingsByKeyPath];
NSDictionary* keyPathsAndObjectMappings = [self.mappingProvider mappingsByKeyPath];
for (NSString* keyPath in keyPathsAndObjectMappings) {
id mappingResult;
id mappableValue;
@@ -238,16 +244,16 @@
// Found something to map
foundMappable = YES;
RKObjectMapping* objectMapping = [keyPathsAndObjectMappings objectForKey:keyPath];
RKObjectAbstractMapping* mapping = [keyPathsAndObjectMappings objectForKey:keyPath];
if ([self.delegate respondsToSelector:@selector(objectMapper:didFindMappableObject:atKeyPath:withMapping:)]) {
[self.delegate objectMapper:self didFindMappableObject:mappableValue atKeyPath:keyPath withMapping:objectMapping];
}
if (objectMapping.forceCollectionMapping || [mappableValue isKindOfClass:[NSArray class]] || [mappableValue isKindOfClass:[NSSet class]]) {
[self.delegate objectMapper:self didFindMappableObject:mappableValue atKeyPath:keyPath withMapping:mapping];
}
if (mapping.forceCollectionMapping || [mappableValue isKindOfClass:[NSArray class]] || [mappableValue isKindOfClass:[NSSet class]]) {
RKLogDebug(@"Found mappable collection at keyPath '%@': %@", keyPath, mappableValue);
mappingResult = [self mapCollection:mappableValue atKeyPath:keyPath usingMapping:objectMapping];
mappingResult = [self mapCollection:mappableValue atKeyPath:keyPath usingMapping:mapping];
} else {
RKLogDebug(@"Found mappable data at keyPath '%@': %@", keyPath, mappableValue);
mappingResult = [self mapObject:mappableValue atKeyPath:keyPath usingMapping:objectMapping];
mappingResult = [self mapObject:mappableValue atKeyPath:keyPath usingMapping:mapping];
}
if (mappingResult) {
@@ -272,14 +278,25 @@
return [RKObjectMappingResult mappingResultWithDictionary:results];
}
#pragma - RKObjectFactory methods
- (id)objectWithMapping:(RKObjectMapping*)objectMapping andData:(id)mappableData {
if (self.objectFactory) {
return [self.objectFactory objectWithMapping:objectMapping andData:mappableData];
- (id)objectWithMapping:(RKObjectAbstractMapping*)abstractMapping andData:(id)mappableData {
NSAssert(! [abstractMapping isMemberOfClass:[RKObjectAbstractMapping class]], @"Expected a concrete subclass of RKObjectAbstractMapping");
RKObjectMapping* objectMapping = nil;
if ([abstractMapping isKindOfClass:[RKObjectPolymorphicMapping class]]) {
objectMapping = [(RKObjectPolymorphicMapping*)abstractMapping objectMappingForDictionary:mappableData];
if (! objectMapping) {
RKLogDebug(@"Mapping %@ declined mapping for data %@: returned nil objectMapping", abstractMapping, mappableData);
}
} else if ([abstractMapping isKindOfClass:[RKObjectMapping class]]) {
objectMapping = (RKObjectMapping*)abstractMapping;
} else {
NSAssert(objectMapping, @"Encountered unknown mapping type '%@'", NSStringFromClass([abstractMapping class]));
}
return [[objectMapping.objectClass new] autorelease];
if (objectMapping) {
return [objectMapping mappableObjectForData:mappableData];
}
return nil;
}
@end

View File

@@ -9,8 +9,9 @@
#import "../Support/Errors.h"
typedef enum RKObjectMapperErrors {
RKObjectMapperErrorObjectMappingNotFound, // No mapping found
RKObjectMapperErrorObjectMappingTypeMismatch, // Target class and object mapping are in disagreement
RKObjectMapperErrorUnmappableContent, // No mappable attributes or relationships were found
RKObjectMapperErrorFromMappingResult
RKObjectMapperErrorObjectMappingNotFound = 1001, // No mapping found
RKObjectMapperErrorObjectMappingTypeMismatch = 1002, // Target class and object mapping are in disagreement
RKObjectMapperErrorUnmappableContent = 1003, // No mappable attributes or relationships were found
RKObjectMapperErrorFromMappingResult = 1004, // The error was returned from the mapping result
RKObjectMapperErrorValidationFailure = 1005 // Generic error code for use when constructing validation errors
} RKObjectMapperErrorCode;

View File

@@ -6,10 +6,11 @@
// Copyright 2011 Two Toasters. All rights reserved.
//
@interface RKObjectMapper (Private) <RKObjectFactory>
@interface RKObjectMapper (Private)
- (id)mapObject:(id)mappableObject atKeyPath:keyPath usingMapping:(RKObjectMapping*)mapping;
- (NSArray*)mapCollection:(NSArray*)mappableObjects atKeyPath:(NSString*)keyPath usingMapping:(RKObjectMapping*)mapping;
- (BOOL)mapFromObject:(id)mappableObject toObject:(id)destinationObject atKeyPath:keyPath usingMapping:(RKObjectMapping*)mapping;
- (id)mapObject:(id)mappableObject atKeyPath:keyPath usingMapping:(RKObjectAbstractMapping*)mapping;
- (NSArray*)mapCollection:(NSArray*)mappableObjects atKeyPath:(NSString*)keyPath usingMapping:(RKObjectAbstractMapping*)mapping;
- (BOOL)mapFromObject:(id)mappableObject toObject:(id)destinationObject atKeyPath:keyPath usingMapping:(RKObjectAbstractMapping*)mapping;
- (id)objectWithMapping:(RKObjectAbstractMapping*)objectMapping andData:(id)mappableData;
@end

View File

@@ -7,6 +7,7 @@
//
#import <Foundation/Foundation.h>
#import "RKObjectAbstractMapping.h"
#import "RKObjectAttributeMapping.h"
#import "RKObjectRelationshipMapping.h"
@@ -30,7 +31,7 @@ relationship. Relationships are processed using an object mapping as well.
Instances of RKObjectMapping are used to configure RKObjectMappingOperation instances, which actually
perform the mapping work. Both object loading and serialization are defined in terms of object mappings.
*/
@interface RKObjectMapping : NSObject {
@interface RKObjectMapping : RKObjectAbstractMapping {
Class _objectClass;
NSMutableArray* _mappings;
NSMutableArray* _dateFormatStrings;
@@ -247,9 +248,9 @@ relationship. Relationships are processed using an object mapping as well.
@param relationshipKey A key-value coding key corresponding to a value in the mappable source object and a property
on the destination class that have the same name.
@param objectMapping An object mapping to use for processing the relationship.
@param objectOrPolymorphicMapping An RKObjectMapping or RKObjectPolymorphic mapping to apply when mapping the relationship
*/
- (void)mapRelationship:(NSString*)relationshipKey withObjectMapping:(RKObjectMapping*)objectMapping;
- (void)mapRelationship:(NSString*)relationshipKey withMapping:(RKObjectAbstractMapping*)objectOrPolymorphicMapping;
/**
Syntactic sugar to improve readability when defining a relationship mapping. Implies that the mapping
@@ -257,7 +258,7 @@ relationship. Relationships are processed using an object mapping as well.
@see mapRelationship:withObjectMapping:
*/
- (void)hasMany:(NSString*)keyPath withObjectMapping:(RKObjectMapping*)mapping;
- (void)hasMany:(NSString*)keyPath withMapping:(RKObjectAbstractMapping*)objectOrPolymorphicMapping;
/**
Syntactic sugar to improve readability when defining a relationship mapping. Implies that the mapping
@@ -265,7 +266,7 @@ relationship. Relationships are processed using an object mapping as well.
@see mapRelationship:withObjectMapping:
*/
- (void)hasOne:(NSString*)keyPath withObjectMapping:(RKObjectMapping*)mapping;
- (void)hasOne:(NSString*)keyPath withMapping:(RKObjectAbstractMapping*)objectOrPolymorphicMapping;
/**
Instantiate and add an RKObjectAttributeMapping instance targeting a keyPath within the mappable
@@ -306,7 +307,7 @@ relationship. Relationships are processed using an object mapping as well.
@param objectMapping An object mapping to use when processing the nested objects
@see RKObjectRelationshipMapping
*/
- (void)mapKeyPath:(NSString *)sourceKeyPath toRelationship:(NSString*)destinationRelationship withObjectMapping:(RKObjectMapping *)objectMapping;
- (void)mapKeyPath:(NSString *)sourceKeyPath toRelationship:(NSString*)destinationRelationship withMapping:(RKObjectAbstractMapping *)objectOrPolymorphicMapping;
/**
Instantiate and add an RKObjectRelationshipMapping instance targeting a keyPath within the mappable
@@ -321,7 +322,7 @@ relationship. Relationships are processed using an object mapping as well.
@see mapKeyPath:toRelationship:withObjectMapping:
*/
- (void)mapKeyPath:(NSString *)relationshipKeyPath toRelationship:(NSString*)keyPath withObjectMapping:(RKObjectMapping *)objectMapping serialize:(BOOL)serialize;
- (void)mapKeyPath:(NSString *)relationshipKeyPath toRelationship:(NSString*)keyPath withMapping:(RKObjectAbstractMapping *)objectOrPolymorphicMapping serialize:(BOOL)serialize;
/**
Quickly define a group of attribute mappings using alternating keyPath and attribute names. You must provide
@@ -397,4 +398,12 @@ relationship. Relationships are processed using an object mapping as well.
*/
- (id)defaultValueForMissingAttribute:(NSString*)attributeName;
/**
Returns an auto-released object that can be used to apply this object mapping
given a set of mappable data. For transient objects, this generally returns an
instance of the objectClass. For Core Data backed persistent objects, mappableData
will be inspected to search for primary key data to lookup existing object instances.
*/
- (id)mappableObjectForData:(id)mappableData;
@end

View File

@@ -8,6 +8,7 @@
#import "RKObjectMapping.h"
#import "RKObjectRelationshipMapping.h"
#import "../Support/RKLog.h"
// Constants
NSString* const RKObjectMappingNestingAttributeKeyName = @"<RK_NESTING_ATTRIBUTE>";
@@ -139,17 +140,17 @@ NSString* const RKObjectMappingNestingAttributeKeyName = @"<RK_NESTING_ATTRIBUTE
[self mapAttributesSet:attributeKeyPaths];
}
- (void)mapKeyPath:(NSString *)relationshipKeyPath toRelationship:(NSString*)keyPath withObjectMapping:(RKObjectMapping *)objectMapping serialize:(BOOL)serialize {
RKObjectRelationshipMapping* mapping = [RKObjectRelationshipMapping mappingFromKeyPath:relationshipKeyPath toKeyPath:keyPath objectMapping:objectMapping reversible:serialize];
- (void)mapKeyPath:(NSString *)relationshipKeyPath toRelationship:(NSString*)keyPath withMapping:(RKObjectAbstractMapping *)objectOrPolymorphicMapping serialize:(BOOL)serialize {
RKObjectRelationshipMapping* mapping = [RKObjectRelationshipMapping mappingFromKeyPath:relationshipKeyPath toKeyPath:keyPath withMapping:objectOrPolymorphicMapping reversible:serialize];
[self addRelationshipMapping:mapping];
}
- (void)mapKeyPath:(NSString *)relationshipKeyPath toRelationship:(NSString*)keyPath withObjectMapping:(RKObjectMapping *)objectMapping {
[self mapKeyPath:relationshipKeyPath toRelationship:keyPath withObjectMapping:objectMapping serialize:YES];
- (void)mapKeyPath:(NSString *)relationshipKeyPath toRelationship:(NSString*)keyPath withMapping:(RKObjectAbstractMapping*)objectOrPolymorphicMapping {
[self mapKeyPath:relationshipKeyPath toRelationship:keyPath withMapping:objectOrPolymorphicMapping serialize:YES];
}
- (void)mapRelationship:(NSString*)relationshipKeyPath withObjectMapping:(RKObjectMapping*)objectMapping {
[self mapKeyPath:relationshipKeyPath toRelationship:relationshipKeyPath withObjectMapping:objectMapping];
- (void)mapRelationship:(NSString*)relationshipKeyPath withMapping:(RKObjectAbstractMapping*)objectOrPolymorphicMapping {
[self mapKeyPath:relationshipKeyPath toRelationship:relationshipKeyPath withMapping:objectOrPolymorphicMapping];
}
- (void)mapKeyPath:(NSString*)sourceKeyPath toAttribute:(NSString*)destinationKeyPath {
@@ -157,12 +158,12 @@ NSString* const RKObjectMappingNestingAttributeKeyName = @"<RK_NESTING_ATTRIBUTE
[self addAttributeMapping:mapping];
}
- (void)hasMany:(NSString*)keyPath withObjectMapping:(RKObjectMapping*)objectMapping {
[self mapRelationship:keyPath withObjectMapping:objectMapping];
- (void)hasMany:(NSString*)keyPath withMapping:(RKObjectAbstractMapping*)objectOrPolymorphicMapping {
[self mapRelationship:keyPath withMapping:objectOrPolymorphicMapping];
}
- (void)hasOne:(NSString*)keyPath withObjectMapping:(RKObjectMapping*)mapping {
[self mapRelationship:keyPath withObjectMapping:mapping];
- (void)hasOne:(NSString*)keyPath withMapping:(RKObjectAbstractMapping*)objectOrPolymorphicMapping {
[self mapRelationship:keyPath withMapping:objectOrPolymorphicMapping];
}
- (void)removeAllMappings {
@@ -189,8 +190,13 @@ NSString* const RKObjectMappingNestingAttributeKeyName = @"<RK_NESTING_ATTRIBUTE
}
for (RKObjectRelationshipMapping* relationshipMapping in self.relationshipMappings) {
if (relationshipMapping.reversible) {
[inverseMapping mapKeyPath:relationshipMapping.destinationKeyPath toRelationship:relationshipMapping.sourceKeyPath withObjectMapping:[relationshipMapping.objectMapping inverseMappingAtDepth:depth+1]];
if (relationshipMapping.reversible) {
RKObjectAbstractMapping* mapping = relationshipMapping.mapping;
if (! [mapping isKindOfClass:[RKObjectMapping class]]) {
RKLogWarning(@"Unable to generate inverse mapping for relationship '%@': %@ relationships cannot be inversed.", relationshipMapping.sourceKeyPath, NSStringFromClass([mapping class]));
continue;
}
[inverseMapping mapKeyPath:relationshipMapping.destinationKeyPath toRelationship:relationshipMapping.sourceKeyPath withMapping:[(RKObjectMapping*)mapping inverseMappingAtDepth:depth+1]];
}
}
@@ -241,4 +247,8 @@ NSString* const RKObjectMappingNestingAttributeKeyName = @"<RK_NESTING_ATTRIBUTE
return nil;
}
- (id)mappableObjectForData:(id)mappableData {
return [[self.objectClass new] autorelease];
}
@end

View File

@@ -8,7 +8,6 @@
#import "RKObjectMapping.h"
#import "RKObjectAttributeMapping.h"
#import "RKObjectFactory.h"
@class RKObjectMappingOperation;
@@ -31,7 +30,6 @@
id _destinationObject;
RKObjectMapping* _objectMapping;
id<RKObjectMappingOperationDelegate> _delegate;
id<RKObjectFactory> _objectFactory;
NSDictionary* _nestedAttributeSubstitution;
NSError* _validationError;
}
@@ -57,22 +55,16 @@
*/
@property (nonatomic, assign) id<RKObjectMappingOperationDelegate> delegate;
/**
An object factory responsible for creating new instances of mappable objects
necessary for the processing of relationship mappings
*/
@property (nonatomic, assign) id<RKObjectFactory> objectFactory;
/**
Create a new mapping operation configured to transform the object representation
in a source object to a new destination object according to an object mapping definition
*/
+ (RKObjectMappingOperation*)mappingOperationFromObject:(id)sourceObject toObject:(id)destinationObject withObjectMapping:(RKObjectMapping*)objectMapping;
+ (RKObjectMappingOperation*)mappingOperationFromObject:(id)sourceObject toObject:(id)destinationObject withMapping:(RKObjectAbstractMapping*)mapping;
/**
Initialize a mapping operation for an object and set of data at a particular key path with an object mapping definition
*/
- (id)initWithSourceObject:(id)sourceObject destinationObject:(id)destinationObject objectMapping:(RKObjectMapping*)objectMapping;
- (id)initWithSourceObject:(id)sourceObject destinationObject:(id)destinationObject mapping:(RKObjectAbstractMapping*)mapping;
/**
Process all mappable values from the mappable dictionary and assign them to the target object

View File

@@ -22,29 +22,64 @@
extern NSString* const RKObjectMappingNestingAttributeKeyName;
// Temporary home for object equivalancy tests
BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue) {
NSCAssert(sourceValue, @"Expected sourceValue not to be nil");
NSCAssert(destinationValue, @"Expected destinationValue not to be nil");
SEL comparisonSelector;
if ([sourceValue isKindOfClass:[NSString class]]) {
comparisonSelector = @selector(isEqualToString:);
} else if ([sourceValue isKindOfClass:[NSNumber class]]) {
comparisonSelector = @selector(isEqualToNumber:);
} else if ([sourceValue isKindOfClass:[NSDate class]]) {
comparisonSelector = @selector(isEqualToDate:);
} else if ([sourceValue isKindOfClass:[NSArray class]]) {
comparisonSelector = @selector(isEqualToArray:);
} else if ([sourceValue isKindOfClass:[NSDictionary class]]) {
comparisonSelector = @selector(isEqualToDictionary:);
} else if ([sourceValue 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(sourceValue, comparisonSelector, destinationValue);
}
@implementation RKObjectMappingOperation
@synthesize sourceObject = _sourceObject;
@synthesize destinationObject = _destinationObject;
@synthesize objectMapping = _objectMapping;
@synthesize delegate = _delegate;
@synthesize objectFactory = _objectFactory;
+ (RKObjectMappingOperation*)mappingOperationFromObject:(id)sourceObject toObject:(id)destinationObject withObjectMapping:(RKObjectMapping*)objectMapping {
return [[[self alloc] initWithSourceObject:sourceObject destinationObject:destinationObject objectMapping:objectMapping] autorelease];
+ (RKObjectMappingOperation*)mappingOperationFromObject:(id)sourceObject toObject:(id)destinationObject withMapping:(RKObjectAbstractMapping*)objectMapping {
return [[[self alloc] initWithSourceObject:sourceObject destinationObject:destinationObject mapping:objectMapping] autorelease];
}
- (id)initWithSourceObject:(id)sourceObject destinationObject:(id)destinationObject objectMapping:(RKObjectMapping*)objectMapping {
- (id)initWithSourceObject:(id)sourceObject destinationObject:(id)destinationObject mapping:(RKObjectAbstractMapping*)objectMapping {
NSAssert(sourceObject != nil, @"Cannot perform a mapping operation without a sourceObject object");
NSAssert(destinationObject != nil, @"Cannot perform a mapping operation without a destinationObject");
NSAssert(objectMapping != nil, @"Cannot perform a mapping operation without an object mapping to apply");
NSAssert(objectMapping != nil, @"Cannot perform a mapping operation without a mapping");
self = [super init];
if (self) {
_sourceObject = [sourceObject retain];
_sourceObject = [sourceObject retain];
_destinationObject = [destinationObject retain];
_objectMapping = [objectMapping retain];
}
if ([objectMapping isKindOfClass:[RKObjectPolymorphicMapping class]]) {
_objectMapping = [[(RKObjectPolymorphicMapping*)objectMapping objectMappingForDictionary:_sourceObject] retain];
RKLogDebug(@"RKObjectMappingOperation was initialized with a polymorphic mapping. Determined concrete mapping = %@", _objectMapping);
} else if ([objectMapping isKindOfClass:[RKObjectMapping class]]) {
_objectMapping = (RKObjectMapping*)[objectMapping retain];
}
NSAssert(_objectMapping, @"Cannot perform a mapping operation with an object mapping");
}
return self;
}
@@ -131,32 +166,8 @@ extern NSString* const RKObjectMappingNestingAttributeKeyName;
return nil;
}
- (BOOL)isValue:(id)sourceValue equalToValue:(id)destinationValue {
NSAssert(sourceValue, @"Expected sourceValue not to be nil");
NSAssert(destinationValue, @"Expected destinationValue not to be nil");
SEL comparisonSelector;
if ([sourceValue isKindOfClass:[NSString class]]) {
comparisonSelector = @selector(isEqualToString:);
} else if ([sourceValue isKindOfClass:[NSNumber class]]) {
comparisonSelector = @selector(isEqualToNumber:);
} else if ([sourceValue isKindOfClass:[NSDate class]]) {
comparisonSelector = @selector(isEqualToDate:);
} else if ([sourceValue isKindOfClass:[NSArray class]]) {
comparisonSelector = @selector(isEqualToArray:);
} else if ([sourceValue isKindOfClass:[NSDictionary class]]) {
comparisonSelector = @selector(isEqualToDictionary:);
} else if ([sourceValue 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(sourceValue, comparisonSelector, destinationValue);
- (BOOL)isValue:(id)sourceValue equalToValue:(id)destinationValue {
return RKObjectIsValueEqualToValue(sourceValue, destinationValue);
}
- (BOOL)validateValue:(id)value atKeyPath:(NSString*)keyPath {
@@ -300,12 +311,14 @@ extern NSString* const RKObjectMappingNestingAttributeKeyName;
return ([value isKindOfClass:[NSSet class]] || [value isKindOfClass:[NSArray class]]);
}
- (BOOL)mapNestedObject:(id)anObject toObject:(id)anotherObject withMapping:(RKObjectRelationshipMapping*)mapping {
- (BOOL)mapNestedObject:(id)anObject toObject:(id)anotherObject withRealtionshipMapping:(RKObjectRelationshipMapping*)relationshipMapping {
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");
NSError* error = nil;
RKObjectMappingOperation* subOperation = [RKObjectMappingOperation mappingOperationFromObject:anObject toObject:anotherObject withObjectMapping:mapping.objectMapping];
RKObjectMappingOperation* subOperation = [RKObjectMappingOperation mappingOperationFromObject:anObject toObject:anotherObject withMapping:relationshipMapping.mapping];
subOperation.delegate = self.delegate;
subOperation.objectFactory = self.objectFactory;
if (NO == [subOperation performMapping:&error]) {
RKLogWarning(@"WARNING: Failed mapping nested object: %@", [error localizedDescription]);
}
@@ -317,16 +330,16 @@ extern NSString* const RKObjectMappingNestingAttributeKeyName;
BOOL appliedMappings = NO;
id destinationObject = nil;
for (RKObjectRelationshipMapping* mapping in [self relationshipMappings]) {
id value = [self.sourceObject valueForKeyPath:mapping.sourceKeyPath];
for (RKObjectRelationshipMapping* relationshipMapping in [self relationshipMappings]) {
id value = [self.sourceObject valueForKeyPath:relationshipMapping.sourceKeyPath];
if (value == nil || value == [NSNull null] || [value isEqual:[NSNull null]]) {
RKLogDebug(@"Did not find mappable relationship value keyPath '%@'", mapping.sourceKeyPath);
RKLogDebug(@"Did not find mappable relationship value keyPath '%@'", relationshipMapping.sourceKeyPath);
// Optionally nil out the property
if ([self.objectMapping setNilForMissingRelationships] && [self shouldSetValue:nil atKeyPath:mapping.destinationKeyPath]) {
RKLogTrace(@"Setting nil for missing relationship value at keyPath '%@'", mapping.sourceKeyPath);
[self.destinationObject setValue:nil forKey:mapping.destinationKeyPath];
if ([self.objectMapping setNilForMissingRelationships] && [self shouldSetValue:nil atKeyPath:relationshipMapping.destinationKeyPath]) {
RKLogTrace(@"Setting nil for missing relationship value at keyPath '%@'", relationshipMapping.sourceKeyPath);
[self.destinationObject setValue:nil forKey:relationshipMapping.destinationKeyPath];
}
continue;
@@ -334,37 +347,57 @@ extern NSString* const RKObjectMappingNestingAttributeKeyName;
if ([self isValueACollection:value]) {
// One to many relationship
RKLogDebug(@"Mapping one to many relationship value at keyPath '%@' to '%@'", mapping.sourceKeyPath, mapping.destinationKeyPath);
RKLogDebug(@"Mapping one to many relationship value at keyPath '%@' to '%@'", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath);
appliedMappings = YES;
destinationObject = [NSMutableArray arrayWithCapacity:[value count]];
for (id nestedObject in value) {
id mappedObject = [self.objectFactory objectWithMapping:mapping.objectMapping andData:nestedObject];
if ([self mapNestedObject:nestedObject toObject:mappedObject withMapping:mapping]) {
for (id nestedObject in value) {
RKObjectAbstractMapping* abstractMapping = relationshipMapping.mapping;
RKObjectMapping* objectMapping = nil;
if ([abstractMapping isKindOfClass:[RKObjectPolymorphicMapping class]]) {
objectMapping = [(RKObjectPolymorphicMapping*)abstractMapping objectMappingForDictionary:nestedObject];
if (! objectMapping) {
RKLogDebug(@"Mapping %@ declined mapping for data %@: returned nil objectMapping", abstractMapping, nestedObject);
continue;
}
} else if ([abstractMapping isKindOfClass:[RKObjectMapping class]]) {
objectMapping = (RKObjectMapping*)abstractMapping;
} else {
NSAssert(objectMapping, @"Encountered unknown mapping type '%@'", NSStringFromClass([abstractMapping class]));
}
id mappedObject = [objectMapping mappableObjectForData:nestedObject];
if ([self mapNestedObject:nestedObject toObject:mappedObject withRealtionshipMapping:relationshipMapping]) {
[destinationObject addObject:mappedObject];
}
}
// Transform from NSSet <-> NSArray if necessary
Class type = [[RKObjectPropertyInspector sharedInspector] typeForProperty:mapping.destinationKeyPath ofClass:[self.destinationObject class]];
Class type = [[RKObjectPropertyInspector sharedInspector] typeForProperty:relationshipMapping.destinationKeyPath ofClass:[self.destinationObject class]];
if (type && NO == [[destinationObject class] isSubclassOfClass:type]) {
destinationObject = [self transformValue:destinationObject atKeyPath:mapping.sourceKeyPath toType:type];
destinationObject = [self transformValue:destinationObject atKeyPath:relationshipMapping.sourceKeyPath toType:type];
}
} else {
// One to one relationship
RKLogDebug(@"Mapping one to one relationship value at keyPath '%@' to '%@'", mapping.sourceKeyPath, mapping.destinationKeyPath);
RKLogDebug(@"Mapping one to one relationship value at keyPath '%@' to '%@'", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath);
destinationObject = [self.objectFactory objectWithMapping:mapping.objectMapping andData:value];
NSAssert(destinationObject, @"Cannot map a relationship without an object factory to create it...");
if ([self mapNestedObject:value toObject:destinationObject withMapping:mapping]) {
RKObjectAbstractMapping* abstractMapping = relationshipMapping.mapping;
RKObjectMapping* objectMapping = nil;
if ([abstractMapping isKindOfClass:[RKObjectPolymorphicMapping class]]) {
objectMapping = [(RKObjectPolymorphicMapping*)abstractMapping objectMappingForDictionary:value];
} else if ([abstractMapping isKindOfClass:[RKObjectMapping class]]) {
objectMapping = (RKObjectMapping*)abstractMapping;
}
NSAssert(objectMapping, @"Encountered unknown mapping type '%@'", NSStringFromClass([abstractMapping class]));
destinationObject = [objectMapping mappableObjectForData:value];
if ([self mapNestedObject:value toObject:destinationObject withRealtionshipMapping:relationshipMapping]) {
appliedMappings = YES;
}
}
// If the relationship has changed, set it
if ([self shouldSetValue:destinationObject atKeyPath:mapping.destinationKeyPath]) {
RKLogTrace(@"Mapped relationship object from keyPath '%@' to '%@'. Value: %@", mapping.sourceKeyPath, mapping.destinationKeyPath, destinationObject);
[self.destinationObject setValue:destinationObject forKey:mapping.destinationKeyPath];
if ([self shouldSetValue:destinationObject atKeyPath:relationshipMapping.destinationKeyPath]) {
RKLogTrace(@"Mapped relationship object from keyPath '%@' to '%@'. Value: %@", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath, destinationObject);
[self.destinationObject setValue:destinationObject forKey:relationshipMapping.destinationKeyPath];
}
// Fail out if a validation error has occurred

View File

@@ -7,6 +7,7 @@
//
#import "RKObjectMapping.h"
#import "RKObjectPolymorphicMapping.h"
/**
Responsible for providing object mappings to an instance of the object mapper
@@ -14,33 +15,27 @@
*/
@interface RKObjectMappingProvider : NSObject {
NSMutableArray* _objectMappings;
NSMutableDictionary* _objectMappingsByKeyPath;
NSMutableDictionary* _mappingsByKeyPath;
NSMutableDictionary* _serializationMappings;
}
/**
Set a mapping for a keypath that comes back in your payload
@deprecated
Instructs the mapping provider to use the mapping provided when it encounters content at the specified
key path
*/
- (void)setMapping:(RKObjectMapping*)mapping forKeyPath:(NSString*)keyPath DEPRECATED_ATTRIBUTE;
- (void)setMapping:(RKObjectAbstractMapping*)objectOrPolymorphicMapping forKeyPath:(NSString*)keyPath;
/**
Configure an object mapping to handle data that appears at a particular keyPath in
a payload loaded from a
Returns the RKObjectMapping or RKObjectPolymorphic mapping configured for use
when mappable content is encountered at keyPath
*/
- (void)setObjectMapping:(RKObjectMapping*)mapping forKeyPath:(NSString*)keyPath;
- (RKObjectAbstractMapping*)mappingForKeyPath:(NSString*)keyPath;
/**
Returns the object mapping to use for mapping the specified keyPath into an object graph
Returns a dictionary where the keys are mappable keyPaths and the values are the RKObjectMapping
or RKObjectPolymorphic mappings to use for mappable data that appears at the keyPath.
*/
- (RKObjectMapping*)objectMappingForKeyPath:(NSString*)keyPath;
/**
Returns a dictionary where the keys are mappable keyPaths and the values are the object
mapping to use for objects that appear at the keyPath.
*/
- (NSDictionary*)objectMappingsByKeyPath;
- (NSDictionary*)mappingsByKeyPath;
/**
Registers an object mapping as being rooted at a specific keyPath. The keyPath will be registered
@@ -111,4 +106,30 @@
*/
- (RKObjectMapping*)serializationMappingForClass:(Class)objectClass;
////////////////////////////////////////////////////////////////////////////////////
/// @name Deprecated Object Mapping Methods
/**
Configure an object mapping to handle data that appears at a particular keyPath in
a payload loaded from a
@deprecated
*/
- (void)setObjectMapping:(RKObjectMapping*)mapping forKeyPath:(NSString*)keyPath DEPRECATED_ATTRIBUTE;
/**
Returns the object mapping to use for mapping the specified keyPath into an object graph
@deprecated
*/
- (RKObjectMapping*)objectMappingForKeyPath:(NSString*)keyPath DEPRECATED_ATTRIBUTE;
/**
Returns a dictionary where the keys are mappable keyPaths and the values are the object
mapping to use for objects that appear at the keyPath.
@deprecated
*/
- (NSDictionary*)objectMappingsByKeyPath DEPRECATED_ATTRIBUTE;
@end

View File

@@ -13,7 +13,7 @@
- (id)init {
if ((self = [super init])) {
_objectMappings = [NSMutableArray new];
_objectMappingsByKeyPath = [NSMutableDictionary new];
_mappingsByKeyPath = [NSMutableDictionary new];
_serializationMappings = [NSMutableDictionary new];
}
return self;
@@ -21,21 +21,17 @@
- (void)dealloc {
[_objectMappings release];
[_objectMappingsByKeyPath release];
[_mappingsByKeyPath release];
[_serializationMappings release];
[super dealloc];
}
- (RKObjectMapping*)objectMappingForKeyPath:(NSString*)keyPath {
return [_objectMappingsByKeyPath objectForKey:keyPath];
}
- (void)setMapping:(RKObjectMapping*)mapping forKeyPath:(NSString*)keyPath {
[_objectMappingsByKeyPath setValue:mapping forKey:keyPath];
[_mappingsByKeyPath setValue:mapping forKey:keyPath];
}
- (void)setObjectMapping:(RKObjectMapping*)mapping forKeyPath:(NSString*)keyPath {
[_objectMappingsByKeyPath setValue:mapping forKey:keyPath];
- (RKObjectAbstractMapping*)mappingForKeyPath:(NSString*)keyPath {
return [_mappingsByKeyPath objectForKey:keyPath];
}
- (void)setSerializationMapping:(RKObjectMapping *)mapping forClass:(Class)objectClass {
@@ -46,14 +42,14 @@
return (RKObjectMapping*)[_serializationMappings objectForKey:NSStringFromClass(objectClass)];
}
- (NSDictionary*)objectMappingsByKeyPath {
return _objectMappingsByKeyPath;
- (NSDictionary*)mappingsByKeyPath {
return _mappingsByKeyPath;
}
- (void)registerMapping:(RKObjectMapping*)objectMapping withRootKeyPath:(NSString*)keyPath {
// TODO: Should generate logs
objectMapping.rootKeyPath = keyPath;
[self setObjectMapping:objectMapping forKeyPath:keyPath];
[self setMapping:objectMapping forKeyPath:keyPath];
RKObjectMapping* inverseMapping = [objectMapping inverseMapping];
inverseMapping.rootKeyPath = keyPath;
[self setSerializationMapping:inverseMapping forClass:objectMapping.objectClass];
@@ -65,7 +61,7 @@
- (NSArray*)objectMappingsForClass:(Class)theClass {
NSMutableArray* mappings = [NSMutableArray array];
NSArray* mappingsToSearch = [[NSArray arrayWithArray:_objectMappings] arrayByAddingObjectsFromArray:[_objectMappingsByKeyPath allValues]];
NSArray* mappingsToSearch = [[NSArray arrayWithArray:_objectMappings] arrayByAddingObjectsFromArray:[_mappingsByKeyPath allValues]];
for (RKObjectMapping* objectMapping in mappingsToSearch) {
if (objectMapping.objectClass == theClass && ![mappings containsObject:objectMapping]) {
[mappings addObject:objectMapping];
@@ -80,4 +76,18 @@
return ([objectMappings count] > 0) ? [objectMappings objectAtIndex:0] : nil;
}
#pragma mark - Deprecated
- (RKObjectMapping*)objectMappingForKeyPath:(NSString*)keyPath {
return (RKObjectMapping*) [self mappingForKeyPath:keyPath];
}
- (void)setObjectMapping:(RKObjectMapping*)mapping forKeyPath:(NSString*)keyPath {
[self setMapping:mapping forKeyPath:keyPath];
}
- (NSDictionary*)objectMappingsByKeyPath {
return [self mappingsByKeyPath];
}
@end

View File

@@ -0,0 +1,100 @@
//
// RKDynamicObjectMapping.h
// RestKit
//
// Created by Blake Watters on 7/28/11.
// Copyright 2011 Two Toasters. All rights reserved.
//
#import "RKObjectAbstractMapping.h"
#import "RKObjectMapping.h"
/**
Return the appropriate object mapping given a mappable data
*/
@protocol RKObjectPolymorphicMappingDelegate <NSObject>
@required
- (RKObjectMapping*)objectMappingForData:(id)data;
@end
#ifdef NS_BLOCKS_AVAILABLE
typedef RKObjectMapping*(^RKObjectPolymorphicMappingDelegateBlock)(id);
#endif
/**
Defines a polymorphic object mapping that determines the appropriate concrete
object mapping to apply at mapping time. This allows you to map very similar payloads
differently depending on the type of data contained therein.
*/
@interface RKObjectPolymorphicMapping : RKObjectAbstractMapping {
NSMutableArray* _matchers;
id<RKObjectPolymorphicMappingDelegate> _delegate;
#ifdef NS_BLOCKS_AVAILABLE
RKObjectPolymorphicMappingDelegateBlock _delegateBlock;
#endif
BOOL _forceCollectionMapping;
}
/**
A delegate to call back to determine the appropriate concrete object mapping
to apply to the mappable data.
@see RKDynamicObjectMappingDelegate
*/
@property (nonatomic, assign) id<RKObjectPolymorphicMappingDelegate> delegate;
#ifdef NS_BLOCKS_AVAILABLE
/**
A block to invoke to determine the appropriate concrete object mapping
to apply to the mappable data.
*/
@property (nonatomic, copy) RKObjectPolymorphicMappingDelegateBlock delegateBlock;
#endif
/**
When YES, an NSDictionary encountered by RKObjectMapper will be treated as a collection
rather than as a single mappable entity. This is used to perform sub-keypath mapping wherein
the keys of the dictionary are part of the mappable data.
*/
@property (nonatomic, assign) BOOL forceCollectionMapping;
/**
Return a new auto-released polymorphic object mapping
*/
+ (RKObjectPolymorphicMapping*)polymorphicMapping;
#if NS_BLOCKS_AVAILABLE
/**
Return a new auto-released polymorphic object mapping after yielding it to the block for configuration
*/
+ (RKObjectPolymorphicMapping*)polymorphicMappingWithBlock:(void(^)(RKObjectPolymorphicMapping*))block;
#endif
//+ (id)mappingForClass:(Class)objectClass block:(void(^)(RKObjectMapping*))block {
// TODO: polymorphicMappingWithBlock
/**
Defines a polymorphic mapping rule stating that when the value of the key property matches the specified
value, the objectMapping should be used.
For example, suppose that we have a JSON fragment for a person that we want to map differently based on
the gender of the person. When the gender is 'male', we want to use the Boy class and when then the gender
is 'female' we want to use the Girl class. We might define our polymorphic mapping like so:
RKObjectPolymorphicMapping* mapping = [RKObjectPolymorphicMapping polymorphicMapping];
[mapping setObjectMapping:boyMapping whenValueOfKey:@"gender" isEqualTo:@"male"];
[mapping setObjectMapping:boyMapping whenValueOfKey:@"gender" isEqualTo:@"female"];
*/
- (void)setObjectMapping:(RKObjectMapping*)objectMapping whenValueOfKey:(NSString*)key isEqualTo:(id)value;
/**
Invoked by the RKObjectMapper and RKObjectMappingOperation to determine the appropriate RKObjectMapping to use
when mapping the specified dictionary of mappable data.
*/
- (RKObjectMapping*)objectMappingForDictionary:(NSDictionary*)dictionary;
@end

View File

@@ -0,0 +1,140 @@
//
// RKDynamicObjectMapping.m
// RestKit
//
// Created by Blake Watters on 7/28/11.
// Copyright 2011 Two Toasters. All rights reserved.
//
#import "RKObjectPolymorphicMapping.h"
#import "../Support/RKLog.h"
// Set Logging Component
#undef RKLogComponent
#define RKLogComponent lcl_cRestKitObjectMapping
// Implemented in RKObjectMappingOperation
BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue);
@interface RKObjectPolymorphicMappingMatcher : NSObject {
NSString* _key;
id _value;
RKObjectMapping* _objectMapping;
}
@property (nonatomic, readonly) RKObjectMapping* objectMapping;
- (id)initWithKey:(NSString*)key value:(id)value objectMapping:(RKObjectMapping*)objectMapping;
- (BOOL)isMatchForData:(id)data;
- (NSString*)matchDescription;
@end
@implementation RKObjectPolymorphicMappingMatcher
@synthesize objectMapping = _objectMapping;
- (id)initWithKey:(NSString*)key value:(id)value objectMapping:(RKObjectMapping*)objectMapping {
self = [super init];
if (self) {
_key = [key retain];
_value = [value retain];
_objectMapping = [objectMapping retain];
}
return self;
}
- (void)dealloc {
[_key release];
[_value release];
[_objectMapping release];
[super dealloc];
}
- (BOOL)isMatchForData:(id)data {
return RKObjectIsValueEqualToValue([data valueForKey:_key], _value);
}
- (NSString*)matchDescription {
return [NSString stringWithFormat:@"%@ == %@", _key, _value];
}
@end
///////////////////////////////////////////////////////////////////////////////////////////////////
@implementation RKObjectPolymorphicMapping
@synthesize delegate = _delegate;
@synthesize delegateBlock = _delegateBlock;
@synthesize forceCollectionMapping = _forceCollectionMapping;
+ (RKObjectPolymorphicMapping*)polymorphicMapping {
return [[self new] autorelease];
}
#if NS_BLOCKS_AVAILABLE
+ (RKObjectPolymorphicMapping*)polymorphicMappingWithBlock:(void(^)(RKObjectPolymorphicMapping*))block {
RKObjectPolymorphicMapping* mapping = [self polymorphicMapping];
block(mapping);
return mapping;
}
#endif
- (id)init {
self = [super init];
if (self) {
_matchers = [NSMutableArray new];
}
return self;
}
- (void)dealloc {
[_matchers release];
[super dealloc];
}
- (void)setObjectMapping:(RKObjectMapping*)objectMapping whenValueOfKey:(NSString*)key isEqualTo:(id)value {
RKLogDebug(@"Adding dynamic object mapping for key '%@' with value '%@' to destination class: %@", key, value, NSStringFromClass(objectMapping.objectClass));
RKObjectPolymorphicMappingMatcher* matcher = [[RKObjectPolymorphicMappingMatcher alloc] initWithKey:key value:value objectMapping:objectMapping];
[_matchers addObject:matcher];
[matcher release];
}
- (RKObjectMapping*)objectMappingForDictionary:(NSDictionary*)data {
NSAssert([data isKindOfClass:[NSDictionary class]], @"Dynamic object mapping can only be performed on NSDictionary mappables, got %@", NSStringFromClass([data class]));
RKObjectMapping* mapping = nil;
RKLogTrace(@"Performing dynamic object mapping for mappable data: %@", data);
// Consult the declarative matchers first
for (RKObjectPolymorphicMappingMatcher* matcher in _matchers) {
if ([matcher isMatchForData:data]) {
RKLogTrace(@"Found declarative match for data: %@.", [matcher matchDescription]);
return matcher.objectMapping;
}
}
// Otherwise consult the delegates
if (self.delegate) {
mapping = [self.delegate objectMappingForData:data];
if (mapping) {
RKLogTrace(@"Found dynamic delegate match. Delegate = %@", self.delegate);
return mapping;
}
}
if (self.delegateBlock) {
mapping = self.delegateBlock(data);
if (mapping) {
RKLogTrace(@"Found dynamic delegateBlock match. DelegateBlock = %@", self.delegateBlock);
}
}
return mapping;
}
@end

View File

@@ -9,18 +9,18 @@
#import <Foundation/Foundation.h>
#import "RKObjectAttributeMapping.h"
@class RKObjectMapping;
@class RKObjectAbstractMapping;
@interface RKObjectRelationshipMapping : RKObjectAttributeMapping {
RKObjectMapping* _objectMapping;
RKObjectAbstractMapping* _mapping;
BOOL _reversible;
}
@property (nonatomic, retain) RKObjectMapping* objectMapping;
@property (nonatomic, retain) RKObjectAbstractMapping* mapping;
@property (nonatomic, assign) BOOL reversible;
+ (RKObjectRelationshipMapping*) mappingFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath objectMapping:(RKObjectMapping*)objectMapping;
+ (RKObjectRelationshipMapping*)mappingFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath withMapping:(RKObjectAbstractMapping*)objectOrPolymorphicMapping;
+ (RKObjectRelationshipMapping*) mappingFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath objectMapping:(RKObjectMapping*)objectMapping reversible:(BOOL)reversible;
+ (RKObjectRelationshipMapping*)mappingFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath withMapping:(RKObjectAbstractMapping*)objectOrPolymorphicMapping reversible:(BOOL)reversible;
@end

View File

@@ -10,30 +10,29 @@
@implementation RKObjectRelationshipMapping
@synthesize objectMapping = _objectMapping;
@synthesize mapping = _mapping;
@synthesize reversible = _reversible;
+ (RKObjectRelationshipMapping*) mappingFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath objectMapping:(RKObjectMapping*)objectMapping reversible:(BOOL)reversible {
RKObjectRelationshipMapping* mapping = (RKObjectRelationshipMapping*) [self mappingFromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath];
mapping.objectMapping = objectMapping;
mapping.reversible = reversible;
return mapping;
+ (RKObjectRelationshipMapping*)mappingFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath withMapping:(RKObjectAbstractMapping*)objectOrPolymorphicMapping reversible:(BOOL)reversible {
RKObjectRelationshipMapping* relationshipMapping = (RKObjectRelationshipMapping*) [self mappingFromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath];
relationshipMapping.reversible = reversible;
relationshipMapping.mapping = objectOrPolymorphicMapping;
return relationshipMapping;
}
+ (RKObjectRelationshipMapping*) mappingFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath objectMapping:(RKObjectMapping*)objectMapping {
RKObjectRelationshipMapping* mapping = [self mappingFromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath objectMapping:objectMapping reversible:YES];
return mapping;
+ (RKObjectRelationshipMapping*)mappingFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath withMapping:(RKObjectAbstractMapping*)objectOrPolymorphicMapping {
return [self mappingFromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath withMapping:objectOrPolymorphicMapping reversible:YES];
}
- (id)copyWithZone:(NSZone *)zone {
RKObjectRelationshipMapping* copy = [super copyWithZone:zone];
copy.objectMapping = self.objectMapping;
copy.mapping = self.mapping;
copy.reversible = self.reversible;
return copy;
}
- (void)dealloc {
[_objectMapping release];
[_mapping release];
[super dealloc];
}

View File

@@ -16,7 +16,7 @@
transformed object is then enclosed in an RKRequestSerializable representation
that is suitable for inclusion in an RKRequest.
*/
@interface RKObjectSerializer : NSObject <RKObjectMappingOperationDelegate, RKObjectFactory> {
@interface RKObjectSerializer : NSObject <RKObjectMappingOperationDelegate> {
id _object;
RKObjectMapping* _mapping;
}

View File

@@ -46,9 +46,8 @@
// Return it serialized into a dictionary
- (id)serializedObject:(NSError**)error {
NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
RKObjectMappingOperation* operation = [RKObjectMappingOperation mappingOperationFromObject:_object toObject:dictionary withObjectMapping:_mapping];
RKObjectMappingOperation* operation = [RKObjectMappingOperation mappingOperationFromObject:_object toObject:dictionary withMapping:_mapping];
operation.delegate = self;
operation.objectFactory = self;
BOOL success = [operation performMapping:error];
if (!success) {
return nil;
@@ -113,11 +112,4 @@
}
}
#pragma mark - RKObjectFactory
// We always serialize back to a dictionary
- (id)objectWithMapping:(RKObjectMapping*)objectMapping andData:(id)mappableData {
return [NSMutableDictionary dictionary];
}
@end