| // |
| // MTLManagedObjectAdapter.m |
| // Mantle |
| // |
| // Created by Justin Spahr-Summers on 2013-03-29. |
| // Copyright (c) 2013 GitHub. All rights reserved. |
| // |
| |
| #import "MTLManagedObjectAdapter.h" |
| #import "EXTScope.h" |
| #import "MTLModel.h" |
| #import "MTLReflection.h" |
| #import "NSArray+MTLManipulationAdditions.h" |
| |
| NSString * const MTLManagedObjectAdapterErrorDomain = @"MTLManagedObjectAdapterErrorDomain"; |
| const NSInteger MTLManagedObjectAdapterErrorNoClassFound = 2; |
| const NSInteger MTLManagedObjectAdapterErrorInitializationFailed = 3; |
| const NSInteger MTLManagedObjectAdapterErrorInvalidManagedObjectKey = 4; |
| const NSInteger MTLManagedObjectAdapterErrorUnsupportedManagedObjectPropertyType = 5; |
| const NSInteger MTLManagedObjectAdapterErrorUnsupportedRelationshipClass = 6; |
| const NSInteger MTLManagedObjectAdapterErrorUniqueFetchRequestFailed = 7; |
| const NSInteger MTLManagedObjectAdapterErrorInvalidManagedObjectMapping = 8; |
| |
| // Performs the given block in the context's queue, if it has one. |
| static id performInContext(NSManagedObjectContext *context, id (^block)(void)) { |
| if (context.concurrencyType == NSConfinementConcurrencyType) { |
| return block(); |
| } |
| |
| __block id result = nil; |
| [context performBlockAndWait:^{ |
| result = block(); |
| }]; |
| |
| return result; |
| } |
| |
| @interface MTLManagedObjectAdapter () |
| |
| // The MTLModel subclass being serialized or deserialized. |
| @property (nonatomic, strong, readonly) Class modelClass; |
| |
| // A cached copy of the return value of +managedObjectKeysByPropertyKey. |
| @property (nonatomic, copy, readonly) NSDictionary *managedObjectKeysByPropertyKey; |
| |
| // A cached copy of the return value of +relationshipModelClassesByPropertyKey. |
| @property (nonatomic, copy, readonly) NSDictionary *relationshipModelClassesByPropertyKey; |
| |
| // Initializes the receiver to serialize or deserialize a MTLModel of the given |
| // class. |
| - (id)initWithModelClass:(Class)modelClass; |
| |
| // Invoked from +modelOfClass:fromManagedObject:processedObjects:error: after |
| // the receiver's properties have been initialized. |
| - (id)modelFromManagedObject:(NSManagedObject *)managedObject processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error; |
| |
| // Performs the actual work of deserialization. This method is also invoked when |
| // processing relationships, to create a new adapter (if needed) to handle them. |
| // |
| // `processedObjects` is a dictionary mapping NSManagedObjects to the MTLModels |
| // that have been created so far. It should remain alive for the full process |
| // of deserializing the top-level managed object. |
| + (id)modelOfClass:(Class)modelClass fromManagedObject:(NSManagedObject *)managedObject processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error; |
| |
| // Invoked from |
| // +managedObjectFromModel:insertingIntoContext:processedObjects:error: after |
| // the receiver's properties have been initialized. |
| - (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error; |
| |
| // Performs the actual work of serialization. This method is also invoked when |
| // processing relationships, to create a new adapter (if needed) to handle them. |
| // |
| // `processedObjects` is a dictionary mapping MTLModels to the NSManagedObjects |
| // that have been created so far. It should remain alive for the full process |
| // of serializing the top-level MTLModel. |
| + (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error; |
| |
| // Looks up the NSValueTransformer that should be used for any attribute that |
| // corresponds the given property key. |
| // |
| // key - The property key to transform from or to. This argument must not be nil. |
| // |
| // Returns a transformer to use, or nil to not transform the property. |
| - (NSValueTransformer *)entityAttributeTransformerForKey:(NSString *)key; |
| |
| // Looks up the managed object key that corresponds to the given key. |
| // |
| // key - The property key to retrieve the corresponding managed object key for. |
| // This argument must not be nil. |
| // |
| // Returns a key to use, or nil to omit the property from managed object |
| // serialization. |
| - (NSString *)managedObjectKeyForKey:(NSString *)key; |
| |
| // Looks at propertyKeysForManagedObjectUniquing and forms an NSPredicate |
| // using the uniquing keys and the provided model. |
| - (NSPredicate *)uniquingPredicateForModel:(MTLModel<MTLManagedObjectSerializing> *)model; |
| |
| @end |
| |
| @implementation MTLManagedObjectAdapter |
| |
| #pragma mark Lifecycle |
| |
| - (id)init { |
| NSAssert(NO, @"%@ should not be initialized using -init", self.class); |
| return nil; |
| } |
| |
| - (id)initWithModelClass:(Class)modelClass { |
| NSParameterAssert(modelClass != nil); |
| |
| self = [super init]; |
| if (self == nil) return nil; |
| |
| _modelClass = modelClass; |
| _managedObjectKeysByPropertyKey = [[modelClass managedObjectKeysByPropertyKey] copy]; |
| |
| if ([modelClass respondsToSelector:@selector(relationshipModelClassesByPropertyKey)]) { |
| _relationshipModelClassesByPropertyKey = [[modelClass relationshipModelClassesByPropertyKey] copy]; |
| } |
| |
| return self; |
| } |
| |
| #pragma mark Serialization |
| |
| - (id)modelFromManagedObject:(NSManagedObject *)managedObject processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error { |
| NSParameterAssert(managedObject != nil); |
| NSParameterAssert(processedObjects != nil); |
| |
| NSEntityDescription *entity = managedObject.entity; |
| NSAssert(entity != nil, @"%@ returned a nil +entity", managedObject); |
| |
| NSManagedObjectContext *context = managedObject.managedObjectContext; |
| |
| NSDictionary *managedObjectProperties = entity.propertiesByName; |
| MTLModel *model = [[self.modelClass alloc] init]; |
| |
| // Pre-emptively consider this object processed, so that we don't get into |
| // any cycles when processing its relationships. |
| CFDictionaryAddValue(processedObjects, (__bridge void *)managedObject, (__bridge void *)model); |
| |
| BOOL (^setValueForKey)(NSString *, id) = ^(NSString *key, id value) { |
| // Mark this as being autoreleased, because validateValue may return |
| // a new object to be stored in this variable (and we don't want ARC to |
| // double-free or leak the old or new values). |
| __autoreleasing id replaceableValue = value; |
| if (![model validateValue:&replaceableValue forKey:key error:error]) return NO; |
| |
| [model setValue:replaceableValue forKey:key]; |
| return YES; |
| }; |
| |
| for (NSString *propertyKey in [self.modelClass propertyKeys]) { |
| NSString *managedObjectKey = [self managedObjectKeyForKey:propertyKey]; |
| if (managedObjectKey == nil) continue; |
| |
| BOOL (^deserializeAttribute)(NSAttributeDescription *) = ^(NSAttributeDescription *attributeDescription) { |
| id value = performInContext(context, ^{ |
| return [managedObject valueForKey:managedObjectKey]; |
| }); |
| |
| NSValueTransformer *attributeTransformer = [self entityAttributeTransformerForKey:propertyKey]; |
| if (attributeTransformer != nil) value = [attributeTransformer reverseTransformedValue:value]; |
| |
| return setValueForKey(propertyKey, value); |
| }; |
| |
| BOOL (^deserializeRelationship)(NSRelationshipDescription *) = ^(NSRelationshipDescription *relationshipDescription) { |
| Class nestedClass = self.relationshipModelClassesByPropertyKey[propertyKey]; |
| if (nestedClass == nil) { |
| [NSException raise:NSInvalidArgumentException format:@"No class specified for decoding relationship at key \"%@\" in managed object %@", managedObjectKey, managedObject]; |
| } |
| |
| if ([relationshipDescription isToMany]) { |
| id models = performInContext(context, ^ id { |
| id relationshipCollection = [managedObject valueForKey:managedObjectKey]; |
| NSMutableArray *models = [NSMutableArray arrayWithCapacity:[relationshipCollection count]]; |
| |
| for (NSManagedObject *nestedObject in relationshipCollection) { |
| MTLModel *model = [self.class modelOfClass:nestedClass fromManagedObject:nestedObject processedObjects:processedObjects error:error]; |
| if (model == nil) return nil; |
| |
| [models addObject:model]; |
| } |
| |
| return models; |
| }); |
| |
| if (models == nil) return NO; |
| if (![relationshipDescription isOrdered]) models = [NSSet setWithArray:models]; |
| |
| return setValueForKey(propertyKey, models); |
| } else { |
| NSManagedObject *nestedObject = performInContext(context, ^{ |
| return [managedObject valueForKey:managedObjectKey]; |
| }); |
| |
| if (nestedObject == nil) return YES; |
| |
| MTLModel *model = [self.class modelOfClass:nestedClass fromManagedObject:nestedObject processedObjects:processedObjects error:error]; |
| if (model == nil) return NO; |
| |
| return setValueForKey(propertyKey, model); |
| } |
| }; |
| |
| BOOL (^deserializeProperty)(NSPropertyDescription *) = ^(NSPropertyDescription *propertyDescription) { |
| if (propertyDescription == nil) { |
| if (error != NULL) { |
| NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"No property by name \"%@\" exists on the entity.", @""), managedObjectKey]; |
| |
| NSDictionary *userInfo = @{ |
| NSLocalizedDescriptionKey: NSLocalizedString(@"Could not deserialize managed object", @""), |
| NSLocalizedFailureReasonErrorKey: failureReason, |
| }; |
| |
| *error = [NSError errorWithDomain:MTLManagedObjectAdapterErrorDomain code:MTLManagedObjectAdapterErrorInvalidManagedObjectKey userInfo:userInfo]; |
| } |
| |
| return NO; |
| } |
| |
| // Jump through some hoops to avoid referencing classes directly. |
| NSString *propertyClassName = NSStringFromClass(propertyDescription.class); |
| if ([propertyClassName isEqual:@"NSAttributeDescription"]) { |
| return deserializeAttribute((id)propertyDescription); |
| } else if ([propertyClassName isEqual:@"NSRelationshipDescription"]) { |
| return deserializeRelationship((id)propertyDescription); |
| } else { |
| if (error != NULL) { |
| NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Property descriptions of class %@ are unsupported.", @""), propertyClassName]; |
| |
| NSDictionary *userInfo = @{ |
| NSLocalizedDescriptionKey: NSLocalizedString(@"Could not deserialize managed object", @""), |
| NSLocalizedFailureReasonErrorKey: failureReason, |
| }; |
| |
| *error = [NSError errorWithDomain:MTLManagedObjectAdapterErrorDomain code:MTLManagedObjectAdapterErrorUnsupportedManagedObjectPropertyType userInfo:userInfo]; |
| } |
| |
| return NO; |
| } |
| }; |
| |
| if (!deserializeProperty(managedObjectProperties[managedObjectKey])) return nil; |
| } |
| |
| return model; |
| } |
| |
| + (id)modelOfClass:(Class)modelClass fromManagedObject:(NSManagedObject *)managedObject error:(NSError **)error { |
| NSSet *propertyKeys = [modelClass propertyKeys]; |
| |
| for (NSString *mappedPropertyKey in [modelClass managedObjectKeysByPropertyKey]) { |
| if ([propertyKeys containsObject:mappedPropertyKey]) continue; |
| |
| if (error != NULL) { |
| NSDictionary *userInfo = @{ |
| NSLocalizedDescriptionKey: NSLocalizedString(@"Invalid entity attribute mapping", nil), |
| NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"%1$@ could not be parsed because its entity attribute mapping contains illegal property keys.", nil), modelClass] |
| }; |
| |
| *error = [NSError errorWithDomain:MTLManagedObjectAdapterErrorDomain code:MTLManagedObjectAdapterErrorInvalidManagedObjectMapping userInfo:userInfo]; |
| } |
| |
| return nil; |
| } |
| |
| CFMutableDictionaryRef processedObjects = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
| if (processedObjects == NULL) return nil; |
| |
| @onExit { |
| CFRelease(processedObjects); |
| }; |
| |
| return [self modelOfClass:modelClass fromManagedObject:managedObject processedObjects:processedObjects error:error]; |
| } |
| |
| + (id)modelOfClass:(Class)modelClass fromManagedObject:(NSManagedObject *)managedObject processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error { |
| NSParameterAssert(modelClass != nil); |
| NSParameterAssert(processedObjects != nil); |
| |
| if (managedObject == nil) return nil; |
| |
| const void *existingModel = CFDictionaryGetValue(processedObjects, (__bridge void *)managedObject); |
| if (existingModel != NULL) { |
| return (__bridge id)existingModel; |
| } |
| |
| if ([modelClass respondsToSelector:@selector(classForDeserializingManagedObject:)]) { |
| modelClass = [modelClass classForDeserializingManagedObject:managedObject]; |
| if (modelClass == nil) { |
| if (error != NULL) { |
| NSDictionary *userInfo = @{ |
| NSLocalizedDescriptionKey: NSLocalizedString(@"Could not deserialize managed object", @""), |
| NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"No model class could be found to deserialize the object.", @"") |
| }; |
| |
| *error = [NSError errorWithDomain:MTLManagedObjectAdapterErrorDomain code:MTLManagedObjectAdapterErrorNoClassFound userInfo:userInfo]; |
| } |
| |
| return nil; |
| } |
| } |
| |
| MTLManagedObjectAdapter *adapter = [[self alloc] initWithModelClass:modelClass]; |
| return [adapter modelFromManagedObject:managedObject processedObjects:processedObjects error:error]; |
| } |
| |
| - (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error { |
| NSParameterAssert(model != nil); |
| NSParameterAssert(context != nil); |
| NSParameterAssert(processedObjects != nil); |
| |
| NSString *entityName = [model.class managedObjectEntityName]; |
| NSAssert(entityName != nil, @"%@ returned a nil +managedObjectEntityName", model.class); |
| |
| Class entityDescriptionClass = NSClassFromString(@"NSEntityDescription"); |
| NSAssert(entityDescriptionClass != nil, @"CoreData.framework must be linked to use MTLManagedObjectAdapter"); |
| |
| Class fetchRequestClass = NSClassFromString(@"NSFetchRequest"); |
| NSAssert(fetchRequestClass != nil, @"CoreData.framework must be linked to use MTLManagedObjectAdapter"); |
| |
| // If a uniquing predicate is provided, perform a fetch request to guarantee a unique managed object. |
| __block NSManagedObject *managedObject = nil; |
| NSPredicate *uniquingPredicate = [self uniquingPredicateForModel:model]; |
| |
| if (uniquingPredicate != nil) { |
| __block NSError *fetchRequestError = nil; |
| __block BOOL encountedError = NO; |
| managedObject = performInContext(context, ^ id { |
| NSFetchRequest *fetchRequest = [[fetchRequestClass alloc] init]; |
| fetchRequest.entity = [entityDescriptionClass entityForName:entityName inManagedObjectContext:context]; |
| fetchRequest.predicate = uniquingPredicate; |
| fetchRequest.returnsObjectsAsFaults = NO; |
| fetchRequest.fetchLimit = 1; |
| |
| NSArray *results = [context executeFetchRequest:fetchRequest error:&fetchRequestError]; |
| |
| if (results == nil) { |
| encountedError = YES; |
| if (error != NULL) { |
| NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Failed to fetch a managed object for uniqing predicate \"%@\".", @""), uniquingPredicate]; |
| |
| NSDictionary *userInfo = @{ |
| NSLocalizedDescriptionKey: NSLocalizedString(@"Could not serialize managed object", @""), |
| NSLocalizedFailureReasonErrorKey: failureReason, |
| }; |
| |
| fetchRequestError = [NSError errorWithDomain:MTLManagedObjectAdapterErrorDomain code:MTLManagedObjectAdapterErrorUniqueFetchRequestFailed userInfo:userInfo]; |
| } |
| |
| return nil; |
| } |
| |
| return results.mtl_firstObject; |
| }); |
| |
| if (encountedError && error != NULL) { |
| *error = fetchRequestError; |
| return nil; |
| } |
| } |
| |
| if (managedObject == nil) { |
| managedObject = [entityDescriptionClass insertNewObjectForEntityForName:entityName inManagedObjectContext:context]; |
| } else { |
| // Our CoreData store already has data for this model, we need to merge |
| [self mergeValuesOfModel:model forKeysFromManagedObject:managedObject]; |
| } |
| |
| if (managedObject == nil) { |
| if (error != NULL) { |
| NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Failed to initialize a managed object from entity named \"%@\".", @""), entityName]; |
| |
| NSDictionary *userInfo = @{ |
| NSLocalizedDescriptionKey: NSLocalizedString(@"Could not serialize managed object", @""), |
| NSLocalizedFailureReasonErrorKey: failureReason, |
| }; |
| |
| *error = [NSError errorWithDomain:MTLManagedObjectAdapterErrorDomain code:MTLManagedObjectAdapterErrorInitializationFailed userInfo:userInfo]; |
| } |
| |
| return nil; |
| } |
| |
| // Assign all errors to this variable to work around a memory problem. |
| // |
| // See https://github.com/github/Mantle/pull/120 for more context. |
| __block NSError *tmpError; |
| |
| // Pre-emptively consider this object processed, so that we don't get into |
| // any cycles when processing its relationships. |
| CFDictionaryAddValue(processedObjects, (__bridge void *)model, (__bridge void *)managedObject); |
| |
| NSDictionary *dictionaryValue = model.dictionaryValue; |
| NSDictionary *managedObjectProperties = managedObject.entity.propertiesByName; |
| |
| [dictionaryValue enumerateKeysAndObjectsUsingBlock:^(NSString *propertyKey, id value, BOOL *stop) { |
| NSString *managedObjectKey = [self managedObjectKeyForKey:propertyKey]; |
| if (managedObjectKey == nil) return; |
| if ([value isEqual:NSNull.null]) value = nil; |
| |
| BOOL (^serializeAttribute)(NSAttributeDescription *) = ^(NSAttributeDescription *attributeDescription) { |
| // Mark this as being autoreleased, because validateValue may return |
| // a new object to be stored in this variable (and we don't want ARC to |
| // double-free or leak the old or new values). |
| __autoreleasing id transformedValue = value; |
| |
| NSValueTransformer *attributeTransformer = [self entityAttributeTransformerForKey:propertyKey]; |
| if (attributeTransformer != nil) transformedValue = [attributeTransformer transformedValue:transformedValue]; |
| |
| if (![managedObject validateValue:&transformedValue forKey:managedObjectKey error:&tmpError]) return NO; |
| [managedObject setValue:transformedValue forKey:managedObjectKey]; |
| |
| return YES; |
| }; |
| |
| NSManagedObject * (^objectForRelationshipFromModel)(id) = ^ id (id model) { |
| if (![model isKindOfClass:MTLModel.class] || ![model conformsToProtocol:@protocol(MTLManagedObjectSerializing)]) { |
| NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Property of class %@ cannot be encoded into an NSManagedObject.", @""), [model class]]; |
| |
| NSDictionary *userInfo = @{ |
| NSLocalizedDescriptionKey: NSLocalizedString(@"Could not serialize managed object", @""), |
| NSLocalizedFailureReasonErrorKey: failureReason |
| }; |
| |
| tmpError = [NSError errorWithDomain:MTLManagedObjectAdapterErrorDomain code:MTLManagedObjectAdapterErrorUnsupportedRelationshipClass userInfo:userInfo]; |
| |
| return nil; |
| } |
| |
| return [self.class managedObjectFromModel:model insertingIntoContext:context processedObjects:processedObjects error:&tmpError]; |
| }; |
| |
| BOOL (^serializeRelationship)(NSRelationshipDescription *) = ^(NSRelationshipDescription *relationshipDescription) { |
| if (value == nil) return YES; |
| |
| if ([relationshipDescription isToMany]) { |
| if (![value conformsToProtocol:@protocol(NSFastEnumeration)]) { |
| NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Property of class %@ cannot be encoded into a to-many relationship.", @""), [value class]]; |
| |
| NSDictionary *userInfo = @{ |
| NSLocalizedDescriptionKey: NSLocalizedString(@"Could not serialize managed object", @""), |
| NSLocalizedFailureReasonErrorKey: failureReason |
| }; |
| |
| tmpError = [NSError errorWithDomain:MTLManagedObjectAdapterErrorDomain code:MTLManagedObjectAdapterErrorUnsupportedRelationshipClass userInfo:userInfo]; |
| |
| return NO; |
| } |
| |
| id relationshipCollection; |
| if ([relationshipDescription isOrdered]) { |
| relationshipCollection = [NSMutableOrderedSet orderedSet]; |
| } else { |
| relationshipCollection = [NSMutableSet set]; |
| } |
| |
| for (MTLModel *model in value) { |
| NSManagedObject *nestedObject = objectForRelationshipFromModel(model); |
| if (nestedObject == nil) return NO; |
| |
| [relationshipCollection addObject:nestedObject]; |
| } |
| |
| [managedObject setValue:relationshipCollection forKey:managedObjectKey]; |
| } else { |
| NSManagedObject *nestedObject = objectForRelationshipFromModel(value); |
| if (nestedObject == nil) return NO; |
| |
| [managedObject setValue:nestedObject forKey:managedObjectKey]; |
| } |
| |
| return YES; |
| }; |
| |
| BOOL (^serializeProperty)(NSPropertyDescription *) = ^(NSPropertyDescription *propertyDescription) { |
| if (propertyDescription == nil) { |
| NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"No property by name \"%@\" exists on the entity.", @""), managedObjectKey]; |
| |
| NSDictionary *userInfo = @{ |
| NSLocalizedDescriptionKey: NSLocalizedString(@"Could not serialize managed object", @""), |
| NSLocalizedFailureReasonErrorKey: failureReason |
| }; |
| |
| tmpError = [NSError errorWithDomain:MTLManagedObjectAdapterErrorDomain code:MTLManagedObjectAdapterErrorInvalidManagedObjectKey userInfo:userInfo]; |
| |
| return NO; |
| } |
| |
| // Jump through some hoops to avoid referencing classes directly. |
| NSString *propertyClassName = NSStringFromClass(propertyDescription.class); |
| if ([propertyClassName isEqual:@"NSAttributeDescription"]) { |
| return serializeAttribute((id)propertyDescription); |
| } else if ([propertyClassName isEqual:@"NSRelationshipDescription"]) { |
| return serializeRelationship((id)propertyDescription); |
| } else { |
| NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Property descriptions of class %@ are unsupported.", @""), propertyClassName]; |
| |
| NSDictionary *userInfo = @{ |
| NSLocalizedDescriptionKey: NSLocalizedString(@"Could not serialize managed object", @""), |
| NSLocalizedFailureReasonErrorKey: failureReason |
| }; |
| |
| tmpError = [NSError errorWithDomain:MTLManagedObjectAdapterErrorDomain code:MTLManagedObjectAdapterErrorUnsupportedManagedObjectPropertyType userInfo:userInfo]; |
| |
| return NO; |
| } |
| }; |
| |
| if (!serializeProperty(managedObjectProperties[managedObjectKey])) { |
| performInContext(context, ^ id { |
| [context deleteObject:managedObject]; |
| return nil; |
| }); |
| |
| managedObject = nil; |
| *stop = YES; |
| } |
| }]; |
| |
| if (managedObject != nil && ![managedObject validateForInsert:&tmpError]) { |
| managedObject = performInContext(context, ^ id { |
| [context deleteObject:managedObject]; |
| return nil; |
| }); |
| } |
| |
| if (error != NULL) { |
| *error = tmpError; |
| } |
| |
| return managedObject; |
| } |
| |
| + (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context error:(NSError **)error { |
| CFDictionaryKeyCallBacks keyCallbacks = kCFTypeDictionaryKeyCallBacks; |
| |
| // Compare MTLModel keys using pointer equality, not -isEqual:. |
| keyCallbacks.equal = NULL; |
| |
| CFMutableDictionaryRef processedObjects = CFDictionaryCreateMutable(NULL, 0, &keyCallbacks, &kCFTypeDictionaryValueCallBacks); |
| if (processedObjects == NULL) return nil; |
| |
| @onExit { |
| CFRelease(processedObjects); |
| }; |
| |
| return [self managedObjectFromModel:model insertingIntoContext:context processedObjects:processedObjects error:error]; |
| } |
| |
| + (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error { |
| NSParameterAssert(model != nil); |
| NSParameterAssert(context != nil); |
| NSParameterAssert(processedObjects != nil); |
| |
| const void *existingManagedObject = CFDictionaryGetValue(processedObjects, (__bridge void *)model); |
| if (existingManagedObject != NULL) { |
| return (__bridge id)existingManagedObject; |
| } |
| |
| MTLManagedObjectAdapter *adapter = [[self alloc] initWithModelClass:model.class]; |
| return [adapter managedObjectFromModel:model insertingIntoContext:context processedObjects:processedObjects error:error]; |
| } |
| |
| - (NSValueTransformer *)entityAttributeTransformerForKey:(NSString *)key { |
| NSParameterAssert(key != nil); |
| |
| SEL selector = MTLSelectorWithKeyPattern(key, "EntityAttributeTransformer"); |
| if ([self.modelClass respondsToSelector:selector]) { |
| NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self.modelClass methodSignatureForSelector:selector]]; |
| invocation.target = self.modelClass; |
| invocation.selector = selector; |
| [invocation invoke]; |
| |
| __unsafe_unretained id result = nil; |
| [invocation getReturnValue:&result]; |
| return result; |
| } |
| |
| if ([self.modelClass respondsToSelector:@selector(entityAttributeTransformerForKey:)]) { |
| return [self.modelClass entityAttributeTransformerForKey:key]; |
| } |
| |
| return nil; |
| } |
| |
| - (NSString *)managedObjectKeyForKey:(NSString *)key { |
| NSParameterAssert(key != nil); |
| |
| id managedObjectKey = self.managedObjectKeysByPropertyKey[key]; |
| if ([managedObjectKey isEqual:NSNull.null]) return nil; |
| |
| if (managedObjectKey == nil) { |
| return key; |
| } else { |
| return managedObjectKey; |
| } |
| } |
| |
| - (void)mergeValueOfModel:(MTLModel<MTLManagedObjectSerializing> *)model forKey:(NSString *)key fromManagedObject:(NSManagedObject *)managedObject { |
| [model mergeValueForKey:key fromManagedObject:managedObject]; |
| } |
| |
| - (void)mergeValuesOfModel:(MTLModel<MTLManagedObjectSerializing> *)model forKeysFromManagedObject:(NSManagedObject *)managedObject { |
| if ([model respondsToSelector:@selector(mergeValuesForKeysFromManagedObject:)]) { |
| [model mergeValuesForKeysFromManagedObject:managedObject]; |
| } else if ([model respondsToSelector:@selector(mergeValueForKey:fromManagedObject:)]) { |
| [[model.class managedObjectKeysByPropertyKey] enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *managedObjectKey, BOOL *stop) { |
| [self mergeValueOfModel:model forKey:key fromManagedObject:managedObject]; |
| }]; |
| } |
| } |
| |
| - (NSPredicate *)uniquingPredicateForModel:(MTLModel<MTLManagedObjectSerializing> *)model { |
| if (![self.modelClass respondsToSelector:@selector(propertyKeysForManagedObjectUniquing)]) return nil; |
| |
| NSSet *propertyKeys = [self.modelClass propertyKeysForManagedObjectUniquing]; |
| |
| if (propertyKeys == nil) return nil; |
| |
| NSAssert(propertyKeys.count > 0, @"+propertyKeysForManagedObjectUniquing must not be empty."); |
| |
| NSMutableArray *subpredicates = [NSMutableArray array]; |
| for (NSString *propertyKey in propertyKeys) { |
| NSString *managedObjectKey = [self managedObjectKeyForKey:propertyKey]; |
| |
| NSAssert(managedObjectKey != nil, @"%@ must map to a managed object key.", propertyKey); |
| |
| id transformedValue = [model valueForKeyPath:propertyKey]; |
| |
| NSValueTransformer *attributeTransformer = [self entityAttributeTransformerForKey:propertyKey]; |
| if (attributeTransformer != nil) transformedValue = [attributeTransformer transformedValue:transformedValue]; |
| |
| NSPredicate *subpredicate = [NSPredicate predicateWithFormat:@"%K == %@", managedObjectKey, transformedValue]; |
| [subpredicates addObject:subpredicate]; |
| } |
| |
| return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]; |
| } |
| |
| @end |