暫無描述

AWSMTLManagedObjectAdapter.m 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. //
  2. // MTLManagedObjectAdapter.m
  3. // Mantle
  4. //
  5. // Created by Justin Spahr-Summers on 2013-03-29.
  6. // Copyright (c) 2013 GitHub. All rights reserved.
  7. //
  8. #import "AWSMTLManagedObjectAdapter.h"
  9. #import "AWSEXTScope.h"
  10. #import "AWSMTLModel.h"
  11. #import "AWSMTLReflection.h"
  12. #import "NSArray+AWSMTLManipulationAdditions.h"
  13. NSString * const AWSMTLManagedObjectAdapterErrorDomain = @"AWSMTLManagedObjectAdapterErrorDomain";
  14. const NSInteger AWSMTLManagedObjectAdapterErrorNoClassFound = 2;
  15. const NSInteger AWSMTLManagedObjectAdapterErrorInitializationFailed = 3;
  16. const NSInteger AWSMTLManagedObjectAdapterErrorInvalidManagedObjectKey = 4;
  17. const NSInteger AWSMTLManagedObjectAdapterErrorUnsupportedManagedObjectPropertyType = 5;
  18. const NSInteger AWSMTLManagedObjectAdapterErrorUnsupportedRelationshipClass = 6;
  19. const NSInteger AWSMTLManagedObjectAdapterErrorUniqueFetchRequestFailed = 7;
  20. const NSInteger AWSMTLManagedObjectAdapterErrorInvalidManagedObjectMapping = 8;
  21. // Performs the given block in the context's queue, if it has one.
  22. static id performInContext(NSManagedObjectContext *context, id (^block)(void)) {
  23. #pragma clang diagnostic push
  24. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  25. if (context.concurrencyType == NSConfinementConcurrencyType) {
  26. return block();
  27. }
  28. #pragma clang diagnostic pop
  29. __block id result = nil;
  30. [context performBlockAndWait:^{
  31. result = block();
  32. }];
  33. return result;
  34. }
  35. @interface AWSMTLManagedObjectAdapter ()
  36. // The MTLModel subclass being serialized or deserialized.
  37. @property (nonatomic, strong, readonly) Class modelClass;
  38. // A cached copy of the return value of +managedObjectKeysByPropertyKey.
  39. @property (nonatomic, copy, readonly) NSDictionary *managedObjectKeysByPropertyKey;
  40. // A cached copy of the return value of +relationshipModelClassesByPropertyKey.
  41. @property (nonatomic, copy, readonly) NSDictionary *relationshipModelClassesByPropertyKey;
  42. // Initializes the receiver to serialize or deserialize a MTLModel of the given
  43. // class.
  44. - (id)initWithModelClass:(Class)modelClass;
  45. // Invoked from +modelOfClass:fromManagedObject:processedObjects:error: after
  46. // the receiver's properties have been initialized.
  47. - (id)modelFromManagedObject:(NSManagedObject *)managedObject processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error;
  48. // Performs the actual work of deserialization. This method is also invoked when
  49. // processing relationships, to create a new adapter (if needed) to handle them.
  50. //
  51. // `processedObjects` is a dictionary mapping NSManagedObjects to the MTLModels
  52. // that have been created so far. It should remain alive for the full process
  53. // of deserializing the top-level managed object.
  54. + (id)modelOfClass:(Class)modelClass fromManagedObject:(NSManagedObject *)managedObject processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error;
  55. // Invoked from
  56. // +managedObjectFromModel:insertingIntoContext:processedObjects:error: after
  57. // the receiver's properties have been initialized.
  58. - (id)managedObjectFromModel:(AWSMTLModel<AWSMTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error;
  59. // Performs the actual work of serialization. This method is also invoked when
  60. // processing relationships, to create a new adapter (if needed) to handle them.
  61. //
  62. // `processedObjects` is a dictionary mapping MTLModels to the NSManagedObjects
  63. // that have been created so far. It should remain alive for the full process
  64. // of serializing the top-level MTLModel.
  65. + (id)managedObjectFromModel:(AWSMTLModel<AWSMTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error;
  66. // Looks up the NSValueTransformer that should be used for any attribute that
  67. // corresponds the given property key.
  68. //
  69. // key - The property key to transform from or to. This argument must not be nil.
  70. //
  71. // Returns a transformer to use, or nil to not transform the property.
  72. - (NSValueTransformer *)entityAttributeTransformerForKey:(NSString *)key;
  73. // Looks up the managed object key that corresponds to the given key.
  74. //
  75. // key - The property key to retrieve the corresponding managed object key for.
  76. // This argument must not be nil.
  77. //
  78. // Returns a key to use, or nil to omit the property from managed object
  79. // serialization.
  80. - (NSString *)managedObjectKeyForKey:(NSString *)key;
  81. // Looks at propertyKeysForManagedObjectUniquing and forms an NSPredicate
  82. // using the uniquing keys and the provided model.
  83. - (NSPredicate *)uniquingPredicateForModel:(AWSMTLModel<AWSMTLManagedObjectSerializing> *)model;
  84. @end
  85. @implementation AWSMTLManagedObjectAdapter
  86. #pragma mark Lifecycle
  87. - (id)init {
  88. NSAssert(NO, @"%@ should not be initialized using -init", self.class);
  89. return nil;
  90. }
  91. - (id)initWithModelClass:(Class)modelClass {
  92. NSParameterAssert(modelClass != nil);
  93. self = [super init];
  94. if (self == nil) return nil;
  95. _modelClass = modelClass;
  96. _managedObjectKeysByPropertyKey = [[modelClass managedObjectKeysByPropertyKey] copy];
  97. if ([modelClass respondsToSelector:@selector(relationshipModelClassesByPropertyKey)]) {
  98. _relationshipModelClassesByPropertyKey = [[modelClass relationshipModelClassesByPropertyKey] copy];
  99. }
  100. return self;
  101. }
  102. #pragma mark Serialization
  103. - (id)modelFromManagedObject:(NSManagedObject *)managedObject processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError * __autoreleasing *)error {
  104. NSParameterAssert(managedObject != nil);
  105. NSParameterAssert(processedObjects != nil);
  106. NSEntityDescription *entity = managedObject.entity;
  107. NSAssert(entity != nil, @"%@ returned a nil +entity", managedObject);
  108. NSManagedObjectContext *context = managedObject.managedObjectContext;
  109. NSDictionary *managedObjectProperties = entity.propertiesByName;
  110. AWSMTLModel *model = [[self.modelClass alloc] init];
  111. // Pre-emptively consider this object processed, so that we don't get into
  112. // any cycles when processing its relationships.
  113. CFDictionaryAddValue(processedObjects, (__bridge void *)managedObject, (__bridge void *)model);
  114. BOOL (^setValueForKey)(NSString *, id) = ^(NSString *key, id value) {
  115. // Mark this as being autoreleased, because validateValue may return
  116. // a new object to be stored in this variable (and we don't want ARC to
  117. // double-free or leak the old or new values).
  118. __autoreleasing id replaceableValue = value;
  119. if (![model validateValue:&replaceableValue forKey:key error:error]) return NO;
  120. [model setValue:replaceableValue forKey:key];
  121. return YES;
  122. };
  123. for (NSString *propertyKey in [self.modelClass propertyKeys]) {
  124. NSString *managedObjectKey = [self managedObjectKeyForKey:propertyKey];
  125. if (managedObjectKey == nil) continue;
  126. BOOL (^deserializeAttribute)(NSAttributeDescription *) = ^(NSAttributeDescription *attributeDescription) {
  127. id value = performInContext(context, ^{
  128. return [managedObject valueForKey:managedObjectKey];
  129. });
  130. NSValueTransformer *attributeTransformer = [self entityAttributeTransformerForKey:propertyKey];
  131. if (attributeTransformer != nil) value = [attributeTransformer reverseTransformedValue:value];
  132. return setValueForKey(propertyKey, value);
  133. };
  134. BOOL (^deserializeRelationship)(NSRelationshipDescription *) = ^(NSRelationshipDescription *relationshipDescription) {
  135. Class nestedClass = self.relationshipModelClassesByPropertyKey[propertyKey];
  136. if (nestedClass == nil) {
  137. [NSException raise:NSInvalidArgumentException format:@"No class specified for decoding relationship at key \"%@\" in managed object %@", managedObjectKey, managedObject];
  138. }
  139. if ([relationshipDescription isToMany]) {
  140. id models = performInContext(context, ^ id {
  141. id relationshipCollection = [managedObject valueForKey:managedObjectKey];
  142. NSMutableArray *models = [NSMutableArray arrayWithCapacity:[relationshipCollection count]];
  143. for (NSManagedObject *nestedObject in relationshipCollection) {
  144. AWSMTLModel *model = [self.class modelOfClass:nestedClass fromManagedObject:nestedObject processedObjects:processedObjects error:error];
  145. if (model == nil) return nil;
  146. [models addObject:model];
  147. }
  148. return models;
  149. });
  150. if (models == nil) return NO;
  151. if (![relationshipDescription isOrdered]) models = [NSSet setWithArray:models];
  152. return setValueForKey(propertyKey, models);
  153. } else {
  154. NSManagedObject *nestedObject = performInContext(context, ^{
  155. return [managedObject valueForKey:managedObjectKey];
  156. });
  157. if (nestedObject == nil) return YES;
  158. AWSMTLModel *model = [self.class modelOfClass:nestedClass fromManagedObject:nestedObject processedObjects:processedObjects error:error];
  159. if (model == nil) return NO;
  160. return setValueForKey(propertyKey, model);
  161. }
  162. };
  163. BOOL (^deserializeProperty)(NSPropertyDescription *) = ^(NSPropertyDescription *propertyDescription) {
  164. if (propertyDescription == nil) {
  165. if (error != NULL) {
  166. NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"No property by name \"%@\" exists on the entity.", @""), managedObjectKey];
  167. NSDictionary *userInfo = @{
  168. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not deserialize managed object", @""),
  169. NSLocalizedFailureReasonErrorKey: failureReason,
  170. };
  171. *error = [NSError errorWithDomain:AWSMTLManagedObjectAdapterErrorDomain code:AWSMTLManagedObjectAdapterErrorInvalidManagedObjectKey userInfo:userInfo];
  172. }
  173. return NO;
  174. }
  175. // Jump through some hoops to avoid referencing classes directly.
  176. NSString *propertyClassName = NSStringFromClass(propertyDescription.class);
  177. if ([propertyClassName isEqual:@"NSAttributeDescription"]) {
  178. return deserializeAttribute((id)propertyDescription);
  179. } else if ([propertyClassName isEqual:@"NSRelationshipDescription"]) {
  180. return deserializeRelationship((id)propertyDescription);
  181. } else {
  182. if (error != NULL) {
  183. NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Property descriptions of class %@ are unsupported.", @""), propertyClassName];
  184. NSDictionary *userInfo = @{
  185. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not deserialize managed object", @""),
  186. NSLocalizedFailureReasonErrorKey: failureReason,
  187. };
  188. *error = [NSError errorWithDomain:AWSMTLManagedObjectAdapterErrorDomain code:AWSMTLManagedObjectAdapterErrorUnsupportedManagedObjectPropertyType userInfo:userInfo];
  189. }
  190. return NO;
  191. }
  192. };
  193. if (!deserializeProperty(managedObjectProperties[managedObjectKey])) return nil;
  194. }
  195. return model;
  196. }
  197. + (id)modelOfClass:(Class)modelClass fromManagedObject:(NSManagedObject *)managedObject error:(NSError **)error {
  198. NSSet *propertyKeys = [modelClass propertyKeys];
  199. for (NSString *mappedPropertyKey in [modelClass managedObjectKeysByPropertyKey]) {
  200. if ([propertyKeys containsObject:mappedPropertyKey]) continue;
  201. if (error != NULL) {
  202. NSDictionary *userInfo = @{
  203. NSLocalizedDescriptionKey: NSLocalizedString(@"Invalid entity attribute mapping", nil),
  204. NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"%1$@ could not be parsed because its entity attribute mapping contains illegal property keys.", nil), modelClass]
  205. };
  206. *error = [NSError errorWithDomain:AWSMTLManagedObjectAdapterErrorDomain code:AWSMTLManagedObjectAdapterErrorInvalidManagedObjectMapping userInfo:userInfo];
  207. }
  208. return nil;
  209. }
  210. CFMutableDictionaryRef processedObjects = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
  211. if (processedObjects == NULL) return nil;
  212. @onExit {
  213. CFRelease(processedObjects);
  214. };
  215. return [self modelOfClass:modelClass fromManagedObject:managedObject processedObjects:processedObjects error:error];
  216. }
  217. + (id)modelOfClass:(Class)modelClass fromManagedObject:(NSManagedObject *)managedObject processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error {
  218. NSParameterAssert(modelClass != nil);
  219. NSParameterAssert(processedObjects != nil);
  220. if (managedObject == nil) return nil;
  221. const void *existingModel = CFDictionaryGetValue(processedObjects, (__bridge void *)managedObject);
  222. if (existingModel != NULL) {
  223. return (__bridge id)existingModel;
  224. }
  225. if ([modelClass respondsToSelector:@selector(classForDeserializingManagedObject:)]) {
  226. modelClass = [modelClass classForDeserializingManagedObject:managedObject];
  227. if (modelClass == nil) {
  228. if (error != NULL) {
  229. NSDictionary *userInfo = @{
  230. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not deserialize managed object", @""),
  231. NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"No model class could be found to deserialize the object.", @"")
  232. };
  233. *error = [NSError errorWithDomain:AWSMTLManagedObjectAdapterErrorDomain code:AWSMTLManagedObjectAdapterErrorNoClassFound userInfo:userInfo];
  234. }
  235. return nil;
  236. }
  237. }
  238. AWSMTLManagedObjectAdapter *adapter = [[self alloc] initWithModelClass:modelClass];
  239. return [adapter modelFromManagedObject:managedObject processedObjects:processedObjects error:error];
  240. }
  241. - (id)managedObjectFromModel:(AWSMTLModel<AWSMTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError * __autoreleasing *)error {
  242. NSParameterAssert(model != nil);
  243. NSParameterAssert(context != nil);
  244. NSParameterAssert(processedObjects != nil);
  245. NSString *entityName = [model.class managedObjectEntityName];
  246. NSAssert(entityName != nil, @"%@ returned a nil +managedObjectEntityName", model.class);
  247. Class entityDescriptionClass = NSClassFromString(@"NSEntityDescription");
  248. NSAssert(entityDescriptionClass != nil, @"CoreData.framework must be linked to use MTLManagedObjectAdapter");
  249. Class fetchRequestClass = NSClassFromString(@"NSFetchRequest");
  250. NSAssert(fetchRequestClass != nil, @"CoreData.framework must be linked to use MTLManagedObjectAdapter");
  251. // If a uniquing predicate is provided, perform a fetch request to guarantee a unique managed object.
  252. __block NSManagedObject *managedObject = nil;
  253. NSPredicate *uniquingPredicate = [self uniquingPredicateForModel:model];
  254. if (uniquingPredicate != nil) {
  255. __block NSError *fetchRequestError = nil;
  256. __block BOOL encountedError = NO;
  257. managedObject = performInContext(context, ^ id {
  258. NSFetchRequest *fetchRequest = [[fetchRequestClass alloc] init];
  259. fetchRequest.entity = [entityDescriptionClass entityForName:entityName inManagedObjectContext:context];
  260. fetchRequest.predicate = uniquingPredicate;
  261. fetchRequest.returnsObjectsAsFaults = NO;
  262. fetchRequest.fetchLimit = 1;
  263. NSArray *results = [context executeFetchRequest:fetchRequest error:&fetchRequestError];
  264. if (results == nil) {
  265. encountedError = YES;
  266. if (error != NULL) {
  267. NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Failed to fetch a managed object for uniqing predicate \"%@\".", @""), uniquingPredicate];
  268. NSDictionary *userInfo = @{
  269. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not serialize managed object", @""),
  270. NSLocalizedFailureReasonErrorKey: failureReason,
  271. };
  272. fetchRequestError = [NSError errorWithDomain:AWSMTLManagedObjectAdapterErrorDomain code:AWSMTLManagedObjectAdapterErrorUniqueFetchRequestFailed userInfo:userInfo];
  273. }
  274. return nil;
  275. }
  276. return results.awsmtl_firstObject;
  277. });
  278. if (encountedError && error != NULL) {
  279. *error = fetchRequestError;
  280. return nil;
  281. }
  282. }
  283. if (managedObject == nil) {
  284. managedObject = [entityDescriptionClass insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
  285. } else {
  286. // Our CoreData store already has data for this model, we need to merge
  287. [self mergeValuesOfModel:model forKeysFromManagedObject:managedObject];
  288. }
  289. if (managedObject == nil) {
  290. if (error != NULL) {
  291. NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Failed to initialize a managed object from entity named \"%@\".", @""), entityName];
  292. NSDictionary *userInfo = @{
  293. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not serialize managed object", @""),
  294. NSLocalizedFailureReasonErrorKey: failureReason,
  295. };
  296. *error = [NSError errorWithDomain:AWSMTLManagedObjectAdapterErrorDomain code:AWSMTLManagedObjectAdapterErrorInitializationFailed userInfo:userInfo];
  297. }
  298. return nil;
  299. }
  300. // Assign all errors to this variable to work around a memory problem.
  301. //
  302. // See https://github.com/github/Mantle/pull/120 for more context.
  303. __block NSError *tmpError;
  304. // Pre-emptively consider this object processed, so that we don't get into
  305. // any cycles when processing its relationships.
  306. CFDictionaryAddValue(processedObjects, (__bridge void *)model, (__bridge void *)managedObject);
  307. NSDictionary *dictionaryValue = model.dictionaryValue;
  308. NSDictionary *managedObjectProperties = managedObject.entity.propertiesByName;
  309. [dictionaryValue enumerateKeysAndObjectsUsingBlock:^(NSString *propertyKey, id value, BOOL *stop) {
  310. NSString *managedObjectKey = [self managedObjectKeyForKey:propertyKey];
  311. if (managedObjectKey == nil) return;
  312. if ([value isEqual:NSNull.null]) value = nil;
  313. BOOL (^serializeAttribute)(NSAttributeDescription *) = ^(NSAttributeDescription *attributeDescription) {
  314. // Mark this as being autoreleased, because validateValue may return
  315. // a new object to be stored in this variable (and we don't want ARC to
  316. // double-free or leak the old or new values).
  317. __autoreleasing id transformedValue = value;
  318. NSValueTransformer *attributeTransformer = [self entityAttributeTransformerForKey:propertyKey];
  319. if (attributeTransformer != nil) transformedValue = [attributeTransformer transformedValue:transformedValue];
  320. if (![managedObject validateValue:&transformedValue forKey:managedObjectKey error:&tmpError]) return NO;
  321. [managedObject setValue:transformedValue forKey:managedObjectKey];
  322. return YES;
  323. };
  324. NSManagedObject * (^objectForRelationshipFromModel)(id) = ^ id (id model) {
  325. if (![model isKindOfClass:AWSMTLModel.class] || ![model conformsToProtocol:@protocol(AWSMTLManagedObjectSerializing)]) {
  326. NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Property of class %@ cannot be encoded into an NSManagedObject.", @""), [model class]];
  327. NSDictionary *userInfo = @{
  328. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not serialize managed object", @""),
  329. NSLocalizedFailureReasonErrorKey: failureReason
  330. };
  331. tmpError = [NSError errorWithDomain:AWSMTLManagedObjectAdapterErrorDomain code:AWSMTLManagedObjectAdapterErrorUnsupportedRelationshipClass userInfo:userInfo];
  332. return nil;
  333. }
  334. return [self.class managedObjectFromModel:model insertingIntoContext:context processedObjects:processedObjects error:&tmpError];
  335. };
  336. BOOL (^serializeRelationship)(NSRelationshipDescription *) = ^(NSRelationshipDescription *relationshipDescription) {
  337. if (value == nil) return YES;
  338. if ([relationshipDescription isToMany]) {
  339. if (![value conformsToProtocol:@protocol(NSFastEnumeration)]) {
  340. NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Property of class %@ cannot be encoded into a to-many relationship.", @""), [value class]];
  341. NSDictionary *userInfo = @{
  342. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not serialize managed object", @""),
  343. NSLocalizedFailureReasonErrorKey: failureReason
  344. };
  345. tmpError = [NSError errorWithDomain:AWSMTLManagedObjectAdapterErrorDomain code:AWSMTLManagedObjectAdapterErrorUnsupportedRelationshipClass userInfo:userInfo];
  346. return NO;
  347. }
  348. id relationshipCollection;
  349. if ([relationshipDescription isOrdered]) {
  350. relationshipCollection = [NSMutableOrderedSet orderedSet];
  351. } else {
  352. relationshipCollection = [NSMutableSet set];
  353. }
  354. for (AWSMTLModel *model in value) {
  355. NSManagedObject *nestedObject = objectForRelationshipFromModel(model);
  356. if (nestedObject == nil) return NO;
  357. [relationshipCollection addObject:nestedObject];
  358. }
  359. [managedObject setValue:relationshipCollection forKey:managedObjectKey];
  360. } else {
  361. NSManagedObject *nestedObject = objectForRelationshipFromModel(value);
  362. if (nestedObject == nil) return NO;
  363. [managedObject setValue:nestedObject forKey:managedObjectKey];
  364. }
  365. return YES;
  366. };
  367. BOOL (^serializeProperty)(NSPropertyDescription *) = ^(NSPropertyDescription *propertyDescription) {
  368. if (propertyDescription == nil) {
  369. NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"No property by name \"%@\" exists on the entity.", @""), managedObjectKey];
  370. NSDictionary *userInfo = @{
  371. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not serialize managed object", @""),
  372. NSLocalizedFailureReasonErrorKey: failureReason
  373. };
  374. tmpError = [NSError errorWithDomain:AWSMTLManagedObjectAdapterErrorDomain code:AWSMTLManagedObjectAdapterErrorInvalidManagedObjectKey userInfo:userInfo];
  375. return NO;
  376. }
  377. // Jump through some hoops to avoid referencing classes directly.
  378. NSString *propertyClassName = NSStringFromClass(propertyDescription.class);
  379. if ([propertyClassName isEqual:@"NSAttributeDescription"]) {
  380. return serializeAttribute((id)propertyDescription);
  381. } else if ([propertyClassName isEqual:@"NSRelationshipDescription"]) {
  382. return serializeRelationship((id)propertyDescription);
  383. } else {
  384. NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Property descriptions of class %@ are unsupported.", @""), propertyClassName];
  385. NSDictionary *userInfo = @{
  386. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not serialize managed object", @""),
  387. NSLocalizedFailureReasonErrorKey: failureReason
  388. };
  389. tmpError = [NSError errorWithDomain:AWSMTLManagedObjectAdapterErrorDomain code:AWSMTLManagedObjectAdapterErrorUnsupportedManagedObjectPropertyType userInfo:userInfo];
  390. return NO;
  391. }
  392. };
  393. if (!serializeProperty(managedObjectProperties[managedObjectKey])) {
  394. performInContext(context, ^ id {
  395. [context deleteObject:managedObject];
  396. return nil;
  397. });
  398. managedObject = nil;
  399. *stop = YES;
  400. }
  401. }];
  402. if (managedObject != nil && ![managedObject validateForInsert:&tmpError]) {
  403. managedObject = performInContext(context, ^ id {
  404. [context deleteObject:managedObject];
  405. return nil;
  406. });
  407. }
  408. if (error != NULL) {
  409. *error = tmpError;
  410. }
  411. return managedObject;
  412. }
  413. + (id)managedObjectFromModel:(AWSMTLModel<AWSMTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context error:(NSError **)error {
  414. CFDictionaryKeyCallBacks keyCallbacks = kCFTypeDictionaryKeyCallBacks;
  415. // Compare MTLModel keys using pointer equality, not -isEqual:.
  416. keyCallbacks.equal = NULL;
  417. CFMutableDictionaryRef processedObjects = CFDictionaryCreateMutable(NULL, 0, &keyCallbacks, &kCFTypeDictionaryValueCallBacks);
  418. if (processedObjects == NULL) return nil;
  419. @onExit {
  420. CFRelease(processedObjects);
  421. };
  422. return [self managedObjectFromModel:model insertingIntoContext:context processedObjects:processedObjects error:error];
  423. }
  424. + (id)managedObjectFromModel:(AWSMTLModel<AWSMTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error {
  425. NSParameterAssert(model != nil);
  426. NSParameterAssert(context != nil);
  427. NSParameterAssert(processedObjects != nil);
  428. const void *existingManagedObject = CFDictionaryGetValue(processedObjects, (__bridge void *)model);
  429. if (existingManagedObject != NULL) {
  430. return (__bridge id)existingManagedObject;
  431. }
  432. AWSMTLManagedObjectAdapter *adapter = [[self alloc] initWithModelClass:model.class];
  433. return [adapter managedObjectFromModel:model insertingIntoContext:context processedObjects:processedObjects error:error];
  434. }
  435. - (NSValueTransformer *)entityAttributeTransformerForKey:(NSString *)key {
  436. NSParameterAssert(key != nil);
  437. SEL selector = AWSMTLSelectorWithKeyPattern(key, "EntityAttributeTransformer");
  438. if ([self.modelClass respondsToSelector:selector]) {
  439. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self.modelClass methodSignatureForSelector:selector]];
  440. invocation.target = self.modelClass;
  441. invocation.selector = selector;
  442. [invocation invoke];
  443. __unsafe_unretained id result = nil;
  444. [invocation getReturnValue:&result];
  445. return result;
  446. }
  447. if ([self.modelClass respondsToSelector:@selector(entityAttributeTransformerForKey:)]) {
  448. return [self.modelClass entityAttributeTransformerForKey:key];
  449. }
  450. return nil;
  451. }
  452. - (NSString *)managedObjectKeyForKey:(NSString *)key {
  453. NSParameterAssert(key != nil);
  454. id managedObjectKey = self.managedObjectKeysByPropertyKey[key];
  455. if ([managedObjectKey isEqual:NSNull.null]) return nil;
  456. if (managedObjectKey == nil) {
  457. return key;
  458. } else {
  459. return managedObjectKey;
  460. }
  461. }
  462. - (void)mergeValueOfModel:(AWSMTLModel<AWSMTLManagedObjectSerializing> *)model forKey:(NSString *)key fromManagedObject:(NSManagedObject *)managedObject {
  463. [model mergeValueForKey:key fromManagedObject:managedObject];
  464. }
  465. - (void)mergeValuesOfModel:(AWSMTLModel<AWSMTLManagedObjectSerializing> *)model forKeysFromManagedObject:(NSManagedObject *)managedObject {
  466. if ([model respondsToSelector:@selector(mergeValuesForKeysFromManagedObject:)]) {
  467. [model mergeValuesForKeysFromManagedObject:managedObject];
  468. } else if ([model respondsToSelector:@selector(mergeValueForKey:fromManagedObject:)]) {
  469. [[model.class managedObjectKeysByPropertyKey] enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *managedObjectKey, BOOL *stop) {
  470. [self mergeValueOfModel:model forKey:key fromManagedObject:managedObject];
  471. }];
  472. }
  473. }
  474. - (NSPredicate *)uniquingPredicateForModel:(AWSMTLModel<AWSMTLManagedObjectSerializing> *)model {
  475. if (![self.modelClass respondsToSelector:@selector(propertyKeysForManagedObjectUniquing)]) return nil;
  476. NSSet *propertyKeys = [self.modelClass propertyKeysForManagedObjectUniquing];
  477. if (propertyKeys == nil) return nil;
  478. NSAssert(propertyKeys.count > 0, @"+propertyKeysForManagedObjectUniquing must not be empty.");
  479. NSMutableArray *subpredicates = [NSMutableArray array];
  480. for (NSString *propertyKey in propertyKeys) {
  481. NSString *managedObjectKey = [self managedObjectKeyForKey:propertyKey];
  482. NSAssert(managedObjectKey != nil, @"%@ must map to a managed object key.", propertyKey);
  483. id transformedValue = [model valueForKeyPath:propertyKey];
  484. NSValueTransformer *attributeTransformer = [self entityAttributeTransformerForKey:propertyKey];
  485. if (attributeTransformer != nil) transformedValue = [attributeTransformer transformedValue:transformedValue];
  486. NSPredicate *subpredicate = [NSPredicate predicateWithFormat:@"%K == %@", managedObjectKey, transformedValue];
  487. [subpredicates addObject:subpredicate];
  488. }
  489. return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
  490. }
  491. @end