mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-06-12 17:18:57 +08:00
1. Eliminates use of visitor in favor of aggregating mapping info during mapping. This avoids having to traverse the mapping graph. 1. Eliminate special case handling of cyclic key paths. 1. Eliminate issues with infinite recursion while traversing the mapping graph.
861 lines
44 KiB
Objective-C
861 lines
44 KiB
Objective-C
//
|
|
// RKMappingOperation.m
|
|
// RestKit
|
|
//
|
|
// Created by Blake Watters on 4/30/11.
|
|
// Copyright (c) 2009-2012 RestKit. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
#import "RKMappingOperation.h"
|
|
#import "RKMappingErrors.h"
|
|
#import "RKPropertyInspector.h"
|
|
#import "RKAttributeMapping.h"
|
|
#import "RKRelationshipMapping.h"
|
|
#import "RKErrors.h"
|
|
#import "RKLog.h"
|
|
#import "RKMappingOperationDataSource.h"
|
|
#import "RKObjectMappingOperationDataSource.h"
|
|
#import "RKDynamicMapping.h"
|
|
#import "RKObjectUtilities.h"
|
|
#import "RKValueTransformers.h"
|
|
|
|
// Set Logging Component
|
|
#undef RKLogComponent
|
|
#define RKLogComponent RKlcl_cRestKitObjectMapping
|
|
|
|
extern NSString * const RKObjectMappingNestingAttributeKeyName;
|
|
|
|
// Defined in RKObjectMapping.h
|
|
NSDate *RKDateFromStringWithFormatters(NSString *dateString, NSArray *formatters);
|
|
|
|
/**
|
|
This function ensures that attribute mappings apply cleanly to an `NSMutableDictionary` target class to support mapping to nested keyPaths. See issue #882
|
|
*/
|
|
static void RKSetIntermediateDictionaryValuesOnObjectForKeyPath(id object, NSString *keyPath)
|
|
{
|
|
if (! [object isKindOfClass:[NSMutableDictionary class]]) return;
|
|
NSArray *keyPathComponents = [keyPath componentsSeparatedByString:@"."];
|
|
if ([keyPathComponents count] > 1) {
|
|
for (NSUInteger index = 0; index < [keyPathComponents count] - 1; index++) {
|
|
NSString *intermediateKeyPath = [[keyPathComponents subarrayWithRange:NSMakeRange(0, index + 1)] componentsJoinedByString:@"."];
|
|
if (! [object valueForKeyPath:intermediateKeyPath]) {
|
|
[object setValue:[NSMutableDictionary dictionary] forKeyPath:intermediateKeyPath];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static BOOL RKIsManagedObject(id object)
|
|
{
|
|
Class managedObjectClass = NSClassFromString(@"NSManagedObject");
|
|
return managedObjectClass && [object isKindOfClass:managedObjectClass];
|
|
}
|
|
|
|
id RKTransformedValueWithClass(id value, Class destinationType, NSValueTransformer *dateToStringValueTransformer);
|
|
id RKTransformedValueWithClass(id value, Class destinationType, NSValueTransformer *dateToStringValueTransformer)
|
|
{
|
|
Class sourceType = [value class];
|
|
|
|
if ([value isKindOfClass:destinationType]) {
|
|
// No transformation necessary
|
|
return value;
|
|
} else if ([destinationType isSubclassOfClass:[NSDictionary class]]) {
|
|
return [NSMutableDictionary dictionaryWithObject:[NSMutableDictionary dictionary] forKey:value];
|
|
} else if (RKClassIsCollection(destinationType) && !RKObjectIsCollection(value)) {
|
|
// Call ourself recursively with an array value to transform as appropriate
|
|
return RKTransformedValueWithClass(@[ value ], destinationType, dateToStringValueTransformer);
|
|
} else if ([sourceType isSubclassOfClass:[NSString class]] && [destinationType isSubclassOfClass:[NSDate class]]) {
|
|
// String -> Date
|
|
return [dateToStringValueTransformer transformedValue:value];
|
|
} else if ([destinationType isSubclassOfClass:[NSString class]] && [value isKindOfClass:[NSDate class]]) {
|
|
// NSDate -> NSString
|
|
// Transform using the preferred date formatter
|
|
return [dateToStringValueTransformer reverseTransformedValue:value];
|
|
} else if ([destinationType isSubclassOfClass:[NSData class]]) {
|
|
return [NSKeyedArchiver archivedDataWithRootObject:value];
|
|
} else if ([sourceType isSubclassOfClass:[NSString class]]) {
|
|
if ([destinationType isSubclassOfClass:[NSURL class]]) {
|
|
// String -> URL
|
|
return [NSURL URLWithString:(NSString *)value];
|
|
} else if ([destinationType isSubclassOfClass:[NSDecimalNumber class]]) {
|
|
// String -> Decimal Number
|
|
return [NSDecimalNumber decimalNumberWithString:(NSString *)value];
|
|
} else if ([destinationType isSubclassOfClass:[NSNumber class]]) {
|
|
// String -> Number
|
|
NSString *lowercasedString = [(NSString *)value lowercaseString];
|
|
NSSet *trueStrings = [NSSet setWithObjects:@"true", @"t", @"yes", @"y", nil];
|
|
NSSet *booleanStrings = [trueStrings setByAddingObjectsFromSet:[NSSet setWithObjects:@"false", @"f", @"no", @"n", nil]];
|
|
if ([booleanStrings containsObject:lowercasedString]) {
|
|
// Handle booleans encoded as Strings
|
|
return [NSNumber numberWithBool:[trueStrings containsObject:lowercasedString]];
|
|
} else {
|
|
return [NSNumber numberWithDouble:[(NSString *)value doubleValue]];
|
|
}
|
|
}
|
|
} else if ([value isEqual:[NSNull null]]) {
|
|
// Transform NSNull -> nil for simplicity
|
|
return nil;
|
|
} else if ([sourceType isSubclassOfClass:[NSSet class]]) {
|
|
// Set -> Array
|
|
if ([destinationType isSubclassOfClass:[NSArray class]]) {
|
|
return [(NSSet *)value allObjects];
|
|
}
|
|
} else if ([sourceType isSubclassOfClass:[NSOrderedSet class]]) {
|
|
// OrderedSet -> Array
|
|
if ([destinationType isSubclassOfClass:[NSArray class]]) {
|
|
return [value array];
|
|
}
|
|
} else if ([sourceType isSubclassOfClass:[NSArray class]]) {
|
|
// Array -> Set
|
|
if ([destinationType isSubclassOfClass:[NSSet class]]) {
|
|
return [NSSet setWithArray:value];
|
|
}
|
|
// Array -> OrderedSet
|
|
if ([destinationType isSubclassOfClass:[NSOrderedSet class]]) {
|
|
return [[NSOrderedSet class] orderedSetWithArray:value];
|
|
}
|
|
} else if ([sourceType isSubclassOfClass:[NSNumber class]] && [destinationType isSubclassOfClass:[NSDate class]]) {
|
|
// Number -> Date
|
|
if ([destinationType isSubclassOfClass:[NSDate class]]) {
|
|
return [NSDate dateWithTimeIntervalSince1970:[(NSNumber *)value intValue]];
|
|
} else if ([sourceType isSubclassOfClass:NSClassFromString(@"__NSCFBoolean")] && [destinationType isSubclassOfClass:[NSString class]]) {
|
|
return ([value boolValue] ? @"true" : @"false");
|
|
}
|
|
return [NSDate dateWithTimeIntervalSince1970:[(NSNumber *)value doubleValue]];
|
|
} else if ([sourceType isSubclassOfClass:[NSNumber class]] && [destinationType isSubclassOfClass:[NSDecimalNumber class]]) {
|
|
// Number -> Decimal Number
|
|
return [NSDecimalNumber decimalNumberWithDecimal:[value decimalValue]];
|
|
} else if ( ([sourceType isSubclassOfClass:NSClassFromString(@"__NSCFBoolean")] ||
|
|
[sourceType isSubclassOfClass:NSClassFromString(@"NSCFBoolean")] ) &&
|
|
[destinationType isSubclassOfClass:[NSString class]]) {
|
|
return ([value boolValue] ? @"true" : @"false");
|
|
if ([destinationType isSubclassOfClass:[NSDate class]]) {
|
|
return [NSDate dateWithTimeIntervalSince1970:[(NSNumber *)value intValue]];
|
|
} else if (([sourceType isSubclassOfClass:NSClassFromString(@"__NSCFBoolean")] || [sourceType isSubclassOfClass:NSClassFromString(@"NSCFBoolean")]) && [destinationType isSubclassOfClass:[NSString class]]) {
|
|
return ([value boolValue] ? @"true" : @"false");
|
|
}
|
|
} else if ([destinationType isSubclassOfClass:[NSString class]] && [value respondsToSelector:@selector(stringValue)]) {
|
|
return [value stringValue];
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
// Returns the appropriate value for `nil` value of a primitive type
|
|
static id RKPrimitiveValueForNilValueOfClass(Class keyValueCodingClass)
|
|
{
|
|
if ([keyValueCodingClass isSubclassOfClass:[NSNumber class]]) {
|
|
return @(0);
|
|
} else {
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
// Key comes from: [[_nestedAttributeSubstitution allKeys] lastObject] AND [[_nestedAttributeSubstitution allValues] lastObject];
|
|
NSArray *RKApplyNestingAttributeValueToMappings(NSString *attributeName, id value, NSArray *propertyMappings);
|
|
NSArray *RKApplyNestingAttributeValueToMappings(NSString *attributeName, id value, NSArray *propertyMappings)
|
|
{
|
|
if (!attributeName) return propertyMappings;
|
|
|
|
NSString *searchString = [NSString stringWithFormat:@"(%@)", attributeName];
|
|
NSString *replacementString = [NSString stringWithFormat:@"%@", value];
|
|
NSMutableArray *nestedMappings = [NSMutableArray arrayWithCapacity:[propertyMappings count]];
|
|
for (RKPropertyMapping *propertyMapping in propertyMappings) {
|
|
NSString *sourceKeyPath = [propertyMapping.sourceKeyPath stringByReplacingOccurrencesOfString:searchString withString:replacementString];
|
|
NSString *destinationKeyPath = [propertyMapping.destinationKeyPath stringByReplacingOccurrencesOfString:searchString withString:replacementString];
|
|
RKPropertyMapping *nestedMapping = nil;
|
|
if ([propertyMapping isKindOfClass:[RKAttributeMapping class]]) {
|
|
nestedMapping = [RKAttributeMapping attributeMappingFromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath];
|
|
} else if ([propertyMapping isKindOfClass:[RKRelationshipMapping class]]) {
|
|
nestedMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:sourceKeyPath
|
|
toKeyPath:destinationKeyPath
|
|
withMapping:[(RKRelationshipMapping *)propertyMapping mapping]];
|
|
}
|
|
[nestedMappings addObject:nestedMapping];
|
|
}
|
|
|
|
return nestedMappings;
|
|
}
|
|
|
|
static void RKSetValueForObject(id value, id destinationObject)
|
|
{
|
|
if ([destinationObject isKindOfClass:[NSMutableDictionary class]] && [value isKindOfClass:[NSDictionary class]]) {
|
|
[destinationObject setDictionary:value];
|
|
} else {
|
|
[NSException raise:NSInvalidArgumentException format:@"Unable to set value for destination object of type '%@': no strategy available. %@ to %@", [destinationObject class], value, destinationObject];
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
for (NSString *keyPath in keyPaths) {
|
|
if ([representation valueForKeyPath:keyPath]) return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
@interface RKMappingOperation ()
|
|
@property (nonatomic, strong, readwrite) RKMapping *mapping;
|
|
@property (nonatomic, strong, readwrite) id sourceObject;
|
|
@property (nonatomic, strong, readwrite) id destinationObject;
|
|
@property (nonatomic, strong) NSDictionary *nestedAttributeSubstitution;
|
|
@property (nonatomic, strong, readwrite) NSError *error;
|
|
@property (nonatomic, strong, readwrite) RKObjectMapping *objectMapping; // The concrete mapping
|
|
@property (nonatomic, strong, readwrite) NSMutableDictionary *mutableMappingInfo;
|
|
@end
|
|
|
|
@implementation RKMappingOperation
|
|
|
|
- (id)initWithSourceObject:(id)sourceObject destinationObject:(id)destinationObject mapping:(RKMapping *)objectOrDynamicMapping
|
|
{
|
|
NSAssert(sourceObject != nil, @"Cannot perform a mapping operation without a sourceObject object");
|
|
NSAssert(objectOrDynamicMapping != nil, @"Cannot perform a mapping operation without a mapping");
|
|
|
|
self = [super init];
|
|
if (self) {
|
|
self.sourceObject = sourceObject;
|
|
self.destinationObject = destinationObject;
|
|
self.mapping = objectOrDynamicMapping;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (NSDictionary *)mappingInfo
|
|
{
|
|
return _mutableMappingInfo;
|
|
}
|
|
|
|
- (id)destinationObjectForMappingRepresentation:(id)representation withMapping:(RKMapping *)mapping inRelationship:(RKRelationshipMapping *)relationshipMapping
|
|
{
|
|
RKObjectMapping *concreteMapping = nil;
|
|
if ([mapping isKindOfClass:[RKDynamicMapping class]]) {
|
|
concreteMapping = [(RKDynamicMapping *)mapping objectMappingForRepresentation:representation];
|
|
if (! concreteMapping) {
|
|
RKLogDebug(@"Unable to determine concrete object mapping from dynamic mapping %@ with which to map object representation: %@", mapping, representation);
|
|
return nil;
|
|
}
|
|
} else if ([mapping isKindOfClass:[RKObjectMapping class]]) {
|
|
concreteMapping = (RKObjectMapping *)mapping;
|
|
}
|
|
|
|
return [self.dataSource mappingOperation:self targetObjectForRepresentation:representation withMapping:concreteMapping inRelationship:relationshipMapping];
|
|
}
|
|
|
|
- (NSDate *)parseDateFromString:(NSString *)string
|
|
{
|
|
RKLogTrace(@"Transforming string value '%@' to NSDate...", string);
|
|
return RKDateFromStringWithFormatters(string, self.objectMapping.dateFormatters);
|
|
}
|
|
|
|
- (id)transformValue:(id)value atKeyPath:(NSString *)keyPath toType:(Class)destinationType
|
|
{
|
|
RKLogTrace(@"Found transformable value at keyPath '%@'. Transforming from type '%@' to '%@'", keyPath, NSStringFromClass([value class]), NSStringFromClass(destinationType));
|
|
RKDateToStringValueTransformer *transformer = [[RKDateToStringValueTransformer alloc] initWithDateToStringFormatter:self.objectMapping.preferredDateFormatter stringToDateFormatters:self.objectMapping.dateFormatters];
|
|
id transformedValue = RKTransformedValueWithClass(value, destinationType, transformer);
|
|
if (transformedValue != value) return transformedValue;
|
|
|
|
RKLogWarning(@"Failed transformation of value at keyPath '%@'. No strategy for transforming from '%@' to '%@'", keyPath, NSStringFromClass([value class]), NSStringFromClass(destinationType));
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (BOOL)validateValue:(id *)value atKeyPath:(NSString *)keyPath
|
|
{
|
|
BOOL success = YES;
|
|
|
|
if (self.objectMapping.performKeyValueValidation && [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);
|
|
}
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
- (BOOL)shouldSetValue:(id *)value atKeyPath:(NSString *)keyPath
|
|
{
|
|
id currentValue = [self.destinationObject valueForKeyPath:keyPath];
|
|
if (currentValue == [NSNull null]) {
|
|
currentValue = nil;
|
|
}
|
|
|
|
/*
|
|
WTF - This workaround should not be necessary, but I have been unable to replicate
|
|
the circumstances that trigger it in a unit test to fix elsewhere. The proper place
|
|
to handle it is in transformValue:atKeyPath:toType:
|
|
|
|
See issue & pull request: https://github.com/RestKit/RestKit/pull/436
|
|
*/
|
|
if (*value == [NSNull null]) {
|
|
RKLogWarning(@"Coercing NSNull value to nil in shouldSetValue:atKeyPath: -- should be fixed.");
|
|
*value = nil;
|
|
}
|
|
|
|
if (nil == currentValue && nil == *value) {
|
|
// Both are nil
|
|
return NO;
|
|
} else if (nil == *value || nil == currentValue) {
|
|
// One is nil and the other is not
|
|
return [self validateValue:value atKeyPath:keyPath];
|
|
}
|
|
|
|
if (! RKObjectIsEqualToObject(*value, currentValue)) {
|
|
// Validate value for key
|
|
return [self validateValue:value atKeyPath:keyPath];
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (NSArray *)applyNestingToMappings:(NSArray *)mappings
|
|
{
|
|
if (_nestedAttributeSubstitution) {
|
|
return RKApplyNestingAttributeValueToMappings([[_nestedAttributeSubstitution allKeys] lastObject], [[_nestedAttributeSubstitution allValues] lastObject], mappings);
|
|
}
|
|
|
|
return mappings;
|
|
}
|
|
|
|
- (NSArray *)simpleAttributeMappings
|
|
{
|
|
NSMutableArray *mappings = [NSMutableArray array];
|
|
for (RKAttributeMapping *mapping in [self applyNestingToMappings:self.objectMapping.attributeMappings]) {
|
|
if ([mapping.sourceKeyPath rangeOfString:@"."].location == NSNotFound) {
|
|
[mappings addObject:mapping];
|
|
}
|
|
}
|
|
|
|
return mappings;
|
|
}
|
|
|
|
- (NSArray *)keyPathAttributeMappings
|
|
{
|
|
NSMutableArray *mappings = [NSMutableArray array];
|
|
for (RKAttributeMapping *mapping in [self applyNestingToMappings:self.objectMapping.attributeMappings]) {
|
|
if ([mapping.sourceKeyPath rangeOfString:@"."].location != NSNotFound) {
|
|
[mappings addObject:mapping];
|
|
}
|
|
}
|
|
|
|
return mappings;
|
|
}
|
|
|
|
- (NSArray *)relationshipMappings
|
|
{
|
|
return [self applyNestingToMappings:self.objectMapping.relationshipMappings];
|
|
}
|
|
|
|
- (void)addPropertyMappingToMappingInfo:(RKPropertyMapping *)propertyMapping
|
|
{
|
|
RKPropertyMapping *existingValue = [self.mappingInfo valueForKey:propertyMapping.destinationKeyPath];
|
|
if (existingValue == propertyMapping) return;
|
|
id value = (existingValue) ? @[ existingValue, propertyMapping ] : propertyMapping;
|
|
[self.mutableMappingInfo setObject:value forKey:(propertyMapping.destinationKeyPath ?: [NSNull null])];
|
|
}
|
|
|
|
- (void)applyAttributeMapping:(RKAttributeMapping *)attributeMapping withValue:(id)value
|
|
{
|
|
if ([self.delegate respondsToSelector:@selector(mappingOperation:didFindValue:forKeyPath:mapping:)]) {
|
|
[self.delegate mappingOperation:self didFindValue:value forKeyPath:attributeMapping.sourceKeyPath mapping:attributeMapping];
|
|
}
|
|
RKLogTrace(@"Mapping attribute value keyPath '%@' to '%@'", attributeMapping.sourceKeyPath, attributeMapping.destinationKeyPath);
|
|
|
|
// Inspect the property type to handle any value transformations
|
|
Class type = [self.objectMapping classForKeyPath:attributeMapping.destinationKeyPath];
|
|
if (type && NO == [[value class] isSubclassOfClass:type]) {
|
|
value = [self transformValue:value atKeyPath:attributeMapping.sourceKeyPath toType:type];
|
|
}
|
|
|
|
// If we have a nil value for a primitive property, we need to coerce it into a KVC usable value or bail out
|
|
if (value == nil && RKPropertyInspectorIsPropertyAtKeyPathOfObjectPrimitive(attributeMapping.destinationKeyPath, self.destinationObject)) {
|
|
RKLogDebug(@"Detected `nil` value transformation for primitive property at keyPath '%@'", attributeMapping.destinationKeyPath);
|
|
value = RKPrimitiveValueForNilValueOfClass(type);
|
|
if (! value) {
|
|
RKLogTrace(@"Skipped mapping of attribute value from keyPath '%@ to keyPath '%@' -- Unable to transform `nil` into primitive value representation", attributeMapping.sourceKeyPath, attributeMapping.destinationKeyPath);
|
|
return;
|
|
}
|
|
}
|
|
|
|
RKSetIntermediateDictionaryValuesOnObjectForKeyPath(self.destinationObject, attributeMapping.destinationKeyPath);
|
|
|
|
// Ensure that the value is different
|
|
if ([self shouldSetValue:&value atKeyPath:attributeMapping.destinationKeyPath]) {
|
|
RKLogTrace(@"Mapped attribute value from keyPath '%@' to '%@'. Value: %@", attributeMapping.sourceKeyPath, attributeMapping.destinationKeyPath, value);
|
|
|
|
if (attributeMapping.destinationKeyPath) {
|
|
[self.destinationObject setValue:value forKeyPath:attributeMapping.destinationKeyPath];
|
|
} else {
|
|
RKSetValueForObject(value, self.destinationObject);
|
|
}
|
|
if ([self.delegate respondsToSelector:@selector(mappingOperation:didSetValue:forKeyPath:usingMapping:)]) {
|
|
[self.delegate mappingOperation:self didSetValue:value forKeyPath:attributeMapping.destinationKeyPath usingMapping:attributeMapping];
|
|
}
|
|
[self addPropertyMappingToMappingInfo:attributeMapping];
|
|
} else {
|
|
RKLogTrace(@"Skipped mapping of attribute value from keyPath '%@ to keyPath '%@' -- value is unchanged (%@)", attributeMapping.sourceKeyPath, attributeMapping.destinationKeyPath, value);
|
|
if ([self.delegate respondsToSelector:@selector(mappingOperation:didNotSetUnchangedValue:forKeyPath:usingMapping:)]) {
|
|
[self.delegate mappingOperation:self didNotSetUnchangedValue:value forKeyPath:attributeMapping.destinationKeyPath usingMapping:attributeMapping];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return YES if we mapped any attributes
|
|
- (BOOL)applyAttributeMappings:(NSArray *)attributeMappings
|
|
{
|
|
// If we have a nesting substitution value, we have already succeeded
|
|
BOOL appliedMappings = (_nestedAttributeSubstitution != nil);
|
|
|
|
if (!self.objectMapping.performKeyValueValidation) {
|
|
RKLogDebug(@"Key-value validation is disabled for mapping, skipping...");
|
|
}
|
|
|
|
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);
|
|
continue;
|
|
}
|
|
|
|
id value = (attributeMapping.sourceKeyPath == nil) ? self.sourceObject : [self.sourceObject valueForKeyPath:attributeMapping.sourceKeyPath];
|
|
if (value) {
|
|
appliedMappings = YES;
|
|
[self applyAttributeMapping:attributeMapping withValue:value];
|
|
} else {
|
|
if ([self.delegate respondsToSelector:@selector(mappingOperation:didNotFindValueForKeyPath:mapping:)]) {
|
|
[self.delegate mappingOperation:self didNotFindValueForKeyPath:attributeMapping.sourceKeyPath mapping:attributeMapping];
|
|
}
|
|
RKLogTrace(@"Did not find mappable attribute value keyPath '%@'", attributeMapping.sourceKeyPath);
|
|
|
|
// Optionally set the default value for missing values
|
|
if ([self.objectMapping shouldSetDefaultValueForMissingAttributes]) {
|
|
[self.destinationObject setValue:[self.objectMapping defaultValueForAttribute:attributeMapping.destinationKeyPath]
|
|
forKeyPath:attributeMapping.destinationKeyPath];
|
|
RKLogTrace(@"Setting nil for missing attribute value at keyPath '%@'", attributeMapping.sourceKeyPath);
|
|
}
|
|
}
|
|
|
|
// Fail out if an error has occurred
|
|
if (self.error) break;
|
|
}
|
|
|
|
return appliedMappings;
|
|
}
|
|
|
|
- (BOOL)mapNestedObject:(id)anObject toObject:(id)anotherObject withRelationshipMapping:(RKRelationshipMapping *)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;
|
|
|
|
RKLogTrace(@"Performing nested object mapping using mapping %@ for data: %@", relationshipMapping, anObject);
|
|
RKMappingOperation *subOperation = [[RKMappingOperation alloc] initWithSourceObject:anObject destinationObject:anotherObject mapping:relationshipMapping.mapping];
|
|
subOperation.dataSource = self.dataSource;
|
|
subOperation.delegate = self.delegate;
|
|
[subOperation start];
|
|
|
|
if (subOperation.error) {
|
|
RKLogWarning(@"WARNING: Failed mapping nested object: %@", [error localizedDescription]);
|
|
} else {
|
|
[self addPropertyMappingToMappingInfo:relationshipMapping];
|
|
|
|
// Merge the nested mappingInfo
|
|
NSMutableDictionary *nestedInfo = [NSMutableDictionary dictionaryWithCapacity:[subOperation.mappingInfo count]];
|
|
[subOperation.mappingInfo enumerateKeysAndObjectsUsingBlock:^(NSString *nestedKeyPath, RKPropertyMapping *propertyMapping, BOOL *stop) {
|
|
[nestedInfo setValue:propertyMapping forKey:[NSString stringWithFormat:@"%@.%@", relationshipMapping.destinationKeyPath, nestedKeyPath]];
|
|
}];
|
|
[self.mutableMappingInfo addEntriesFromDictionary:nestedInfo];
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)applyReplaceAssignmentPolicyForRelationshipMapping:(RKRelationshipMapping *)relationshipMapping
|
|
{
|
|
if (relationshipMapping.assignmentPolicy == RKReplaceAssignmentPolicy) {
|
|
if ([self.dataSource respondsToSelector:@selector(mappingOperation:deleteExistingValueOfRelationshipWithMapping:error:)]) {
|
|
NSError *error = nil;
|
|
BOOL success = [self.dataSource mappingOperation:self deleteExistingValueOfRelationshipWithMapping:relationshipMapping error:&error];
|
|
if (! success) {
|
|
RKLogError(@"Failed to delete existing value of relationship mapped with RKReplaceAssignmentPolicy: %@", error);
|
|
self.error = error;
|
|
return NO;
|
|
}
|
|
} else {
|
|
RKLogWarning(@"Requested mapping with `RKReplaceAssignmentPolicy` assignment policy, but the data source does not support it. Mapping has proceeded identically to the `RKSetAssignmentPolicy`.");
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)mapOneToOneRelationshipWithValue:(id)value mapping:(RKRelationshipMapping *)relationshipMapping
|
|
{
|
|
// One to one relationship
|
|
RKLogDebug(@"Mapping one to one relationship value at keyPath '%@' to '%@'", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath);
|
|
|
|
if (relationshipMapping.assignmentPolicy == RKUnionAssignmentPolicy) {
|
|
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Invalid assignment policy: cannot union a one-to-one relationship." };
|
|
self.error = [NSError errorWithDomain:RKErrorDomain code:RKMappingErrorInvalidAssignmentPolicy userInfo:userInfo];
|
|
return NO;
|
|
}
|
|
|
|
id destinationObject = [self destinationObjectForMappingRepresentation:value withMapping:relationshipMapping.mapping inRelationship:relationshipMapping];
|
|
if (! destinationObject) {
|
|
RKLogDebug(@"Mapping %@ declined mapping for representation %@: returned `nil` destination object.", relationshipMapping.mapping, destinationObject);
|
|
return NO;
|
|
}
|
|
[self mapNestedObject:value toObject:destinationObject withRelationshipMapping:relationshipMapping];
|
|
|
|
// If the relationship has changed, set it
|
|
if ([self shouldSetValue:&destinationObject atKeyPath:relationshipMapping.destinationKeyPath]) {
|
|
if (! [self applyReplaceAssignmentPolicyForRelationshipMapping:relationshipMapping]) {
|
|
return NO;
|
|
}
|
|
|
|
RKLogTrace(@"Mapped relationship object from keyPath '%@' to '%@'. Value: %@", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath, destinationObject);
|
|
[self.destinationObject setValue:destinationObject forKey:relationshipMapping.destinationKeyPath];
|
|
} else {
|
|
if ([self.delegate respondsToSelector:@selector(mappingOperation:didNotSetUnchangedValue:forKeyPath:usingMapping:)]) {
|
|
[self.delegate mappingOperation:self didNotSetUnchangedValue:destinationObject forKeyPath:relationshipMapping.destinationKeyPath usingMapping:relationshipMapping];
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)mapCoreDataToManyRelationshipValue:(id)valueForRelationship withMapping:(RKRelationshipMapping *)relationshipMapping
|
|
{
|
|
if (! RKIsManagedObject(self.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];
|
|
[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];
|
|
[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];
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)mapOneToManyRelationshipWithValue:(id)value mapping:(RKRelationshipMapping *)relationshipMapping
|
|
{
|
|
// One to many relationship
|
|
RKLogDebug(@"Mapping one to many relationship value at keyPath '%@' to '%@'", relationshipMapping.sourceKeyPath, relationshipMapping.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);
|
|
}
|
|
|
|
if (relationshipMapping.assignmentPolicy == RKUnionAssignmentPolicy) {
|
|
RKLogDebug(@"Mapping relationship with union assignment policy: constructing combined relationship value.");
|
|
id existingObjects = [self.destinationObject valueForKeyPath:relationshipMapping.destinationKeyPath];
|
|
NSArray *existingObjectsArray = RKTransformedValueWithClass(existingObjects, [NSArray class], nil);
|
|
[relationshipCollection addObjectsFromArray:existingObjectsArray];
|
|
}
|
|
|
|
for (id nestedObject in value) {
|
|
id mappableObject = [self destinationObjectForMappingRepresentation:nestedObject withMapping:relationshipMapping.mapping inRelationship:relationshipMapping];
|
|
if (! mappableObject) {
|
|
RKLogDebug(@"Mapping %@ declined mapping for representation %@: returned `nil` destination object.", relationshipMapping.mapping, nestedObject);
|
|
continue;
|
|
}
|
|
if ([self mapNestedObject:nestedObject toObject:mappableObject withRelationshipMapping:relationshipMapping]) {
|
|
[relationshipCollection addObject:mappableObject];
|
|
}
|
|
}
|
|
|
|
id valueForRelationship = relationshipCollection;
|
|
// Transform from NSSet <-> NSArray if necessary
|
|
Class type = [self.objectMapping classForKeyPath:relationshipMapping.destinationKeyPath];
|
|
if (type && NO == [[relationshipCollection class] isSubclassOfClass:type]) {
|
|
valueForRelationship = [self transformValue:relationshipCollection atKeyPath:relationshipMapping.sourceKeyPath toType:type];
|
|
}
|
|
|
|
// If the relationship has changed, set it
|
|
if ([self shouldSetValue:&valueForRelationship atKeyPath:relationshipMapping.destinationKeyPath]) {
|
|
if (! [self applyReplaceAssignmentPolicyForRelationshipMapping:relationshipMapping]) {
|
|
return NO;
|
|
}
|
|
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];
|
|
}
|
|
} else {
|
|
if ([self.delegate respondsToSelector:@selector(mappingOperation:didNotSetUnchangedValue:forKeyPath:usingMapping:)]) {
|
|
[self.delegate mappingOperation:self didNotSetUnchangedValue:valueForRelationship forKeyPath:relationshipMapping.destinationKeyPath usingMapping:relationshipMapping];
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)applyRelationshipMappings
|
|
{
|
|
NSAssert(self.dataSource, @"Cannot perform relationship mapping without a data source");
|
|
NSMutableArray *mappingsApplied = [NSMutableArray array];
|
|
|
|
for (RKRelationshipMapping *relationshipMapping in [self relationshipMappings]) {
|
|
if ([self isCancelled]) return NO;
|
|
|
|
id value = nil;
|
|
if (relationshipMapping.sourceKeyPath) {
|
|
value = [self.sourceObject valueForKeyPath:relationshipMapping.sourceKeyPath];
|
|
} else {
|
|
// The nil source keyPath indicates that we want to map directly from the parent representation
|
|
value = self.sourceObject;
|
|
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 (! objectMapping) continue; // Mapping declined
|
|
NSArray *propertyKeyPaths = [relationshipMapping valueForKeyPath:@"mapping.propertyMappings.sourceKeyPath"];
|
|
if (! RKObjectContainsValueForKeyPaths(value, propertyKeyPaths)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Track that we applied this mapping
|
|
[mappingsApplied addObject:relationshipMapping];
|
|
|
|
if (value == nil) {
|
|
RKLogDebug(@"Did not find mappable relationship value keyPath '%@'", relationshipMapping.sourceKeyPath);
|
|
|
|
// Optionally nil out the property
|
|
id nilReference = nil;
|
|
if ([self.objectMapping setNilForMissingRelationships] && [self shouldSetValue:&nilReference atKeyPath:relationshipMapping.destinationKeyPath]) {
|
|
RKLogTrace(@"Setting nil for missing relationship value at keyPath '%@'", relationshipMapping.sourceKeyPath);
|
|
[self.destinationObject setValue:nil forKeyPath:relationshipMapping.destinationKeyPath];
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (value == [NSNull null]) {
|
|
RKLogDebug(@"Found null value at keyPath '%@'", relationshipMapping.sourceKeyPath);
|
|
|
|
// Optionally nil out the property
|
|
id nilReference = nil;
|
|
if ([self shouldSetValue:&nilReference atKeyPath:relationshipMapping.destinationKeyPath]) {
|
|
RKLogTrace(@"Setting nil for null relationship value at keyPath '%@'", relationshipMapping.sourceKeyPath);
|
|
[self.destinationObject setValue:nil forKeyPath:relationshipMapping.destinationKeyPath];
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// Handle case where incoming content is collection represented by a dictionary
|
|
if (relationshipMapping.mapping.forceCollectionMapping) {
|
|
// If we have forced mapping of a dictionary, map each subdictionary
|
|
if ([value isKindOfClass:[NSDictionary class]]) {
|
|
RKLogDebug(@"Collection mapping forced for NSDictionary, mapping each key/value independently...");
|
|
NSArray *objectsToMap = [NSMutableArray arrayWithCapacity:[value count]];
|
|
for (id key in value) {
|
|
NSDictionary *dictionaryToMap = [NSDictionary dictionaryWithObject:[value valueForKey:key] forKey:key];
|
|
[(NSMutableArray *)objectsToMap addObject:dictionaryToMap];
|
|
}
|
|
value = objectsToMap;
|
|
} else {
|
|
RKLogWarning(@"Collection mapping forced but mappable objects is of type '%@' rather than NSDictionary", NSStringFromClass([value class]));
|
|
}
|
|
}
|
|
|
|
// Handle case where incoming content is a single object, but we want a collection
|
|
Class relationshipClass = [self.objectMapping classForKeyPath:relationshipMapping.destinationKeyPath];
|
|
BOOL mappingToCollection = RKClassIsCollection(relationshipClass);
|
|
if (mappingToCollection && !RKObjectIsCollection(value)) {
|
|
Class orderedSetClass = NSClassFromString(@"NSOrderedSet");
|
|
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];
|
|
} else if ([relationshipClass isSubclassOfClass:[NSSet class]]) {
|
|
value = [relationshipClass setWithObject:value];
|
|
} else if (orderedSetClass && [relationshipClass isSubclassOfClass:orderedSetClass]) {
|
|
value = [relationshipClass orderedSetWithObject:value];
|
|
} else {
|
|
RKLogWarning(@"Failed to transform single object");
|
|
}
|
|
}
|
|
|
|
BOOL setValueForRelationship;
|
|
if (RKObjectIsCollection(value)) {
|
|
setValueForRelationship = [self mapOneToManyRelationshipWithValue:value mapping:relationshipMapping];
|
|
} else {
|
|
setValueForRelationship = [self mapOneToOneRelationshipWithValue:value mapping:relationshipMapping];
|
|
}
|
|
|
|
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];
|
|
}
|
|
|
|
// Fail out if a validation error has occurred
|
|
if (self.error) break;
|
|
}
|
|
|
|
return [mappingsApplied count] > 0;
|
|
}
|
|
|
|
- (void)applyNestedMappings
|
|
{
|
|
RKAttributeMapping *attributeMapping = [self.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);
|
|
_nestedAttributeSubstitution = @{ attributeMapping.destinationKeyPath: attributeValue };
|
|
[self applyAttributeMapping:attributeMapping withValue:attributeValue];
|
|
} else {
|
|
RKLogWarning(@"Unable to find nesting value for attribute '%@'", attributeMapping.destinationKeyPath);
|
|
}
|
|
}
|
|
|
|
// Serialization
|
|
attributeMapping = [self.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);
|
|
_nestedAttributeSubstitution = @{ attributeMapping.sourceKeyPath: attributeValue };
|
|
} else {
|
|
RKLogWarning(@"Unable to find nesting value for attribute '%@'", attributeMapping.destinationKeyPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)cancel
|
|
{
|
|
[super cancel];
|
|
RKLogDebug(@"Mapping operation cancelled: %@", self);
|
|
}
|
|
|
|
- (void)main
|
|
{
|
|
if ([self isCancelled]) return;
|
|
self.mutableMappingInfo = [NSMutableDictionary dictionary];
|
|
|
|
RKLogDebug(@"Starting mapping operation...");
|
|
RKLogTrace(@"Performing mapping operation: %@", self);
|
|
|
|
if (! self.destinationObject) {
|
|
self.destinationObject = [self destinationObjectForMappingRepresentation:self.sourceObject withMapping:self.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;
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
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);
|
|
|
|
if ([self.delegate respondsToSelector:@selector(mappingOperation:didSelectObjectMapping:forDynamicMapping:)]) {
|
|
[self.delegate mappingOperation:self didSelectObjectMapping:self.objectMapping forDynamicMapping:(RKDynamicMapping *)self.mapping];
|
|
}
|
|
} else if ([self.mapping isKindOfClass:[RKObjectMapping class]]) {
|
|
self.objectMapping = (RKObjectMapping *)self.mapping;
|
|
}
|
|
|
|
[self applyNestedMappings];
|
|
if ([self isCancelled]) return;
|
|
BOOL mappedSimpleAttributes = [self applyAttributeMappings:[self simpleAttributeMappings]];
|
|
if ([self isCancelled]) return;
|
|
BOOL mappedRelationships = [[self relationshipMappings] count] ? [self applyRelationshipMappings] : NO;
|
|
if ([self isCancelled]) return;
|
|
// NOTE: We map key path attributes last to allow you to map across the object graphs for objects created/updated by the relationship mappings
|
|
BOOL mappedKeyPathAttributes = [self applyAttributeMappings:[self keyPathAttributeMappings]];
|
|
|
|
if (!mappedSimpleAttributes && !mappedRelationships && !mappedKeyPathAttributes) {
|
|
// We did not find anything to do
|
|
RKLogDebug(@"Mapping operation did not find any mappable values for the attribute and relationship mappings in the given object representation");
|
|
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"No mappable values found for any of the attributes or relationship mappings" };
|
|
self.error = [NSError errorWithDomain:RKErrorDomain code:RKMappingErrorUnmappableRepresentation userInfo:userInfo];
|
|
}
|
|
|
|
// 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:)]) {
|
|
NSError *error = nil;
|
|
BOOL success = [self.dataSource commitChangesForMappingOperation:self error:&error];
|
|
if (! success) {
|
|
self.error = error;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self.error) {
|
|
if ([self.delegate respondsToSelector:@selector(mappingOperation:didFailWithError:)]) {
|
|
[self.delegate mappingOperation:self didFailWithError:self.error];
|
|
}
|
|
|
|
RKLogError(@"Failed mapping operation: %@", [self.error localizedDescription]);
|
|
} else {
|
|
RKLogDebug(@"Finished mapping operation successfully...");
|
|
}
|
|
}
|
|
|
|
- (BOOL)performMapping:(NSError **)error
|
|
{
|
|
[self start];
|
|
if (error) *error = self.error;
|
|
return self.error == nil;
|
|
}
|
|
|
|
- (NSString *)description
|
|
{
|
|
return [NSString stringWithFormat:@"<%@ %p> for '%@' object. Mapping values from object %@ to object %@ with object mapping %@",
|
|
[self class], self, NSStringFromClass([self.destinationObject class]), self.sourceObject, self.destinationObject, self.objectMapping];
|
|
}
|
|
|
|
@end
|