Sin descripción

AWSMTLModel.m 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. //
  2. // MTLModel.m
  3. // Mantle
  4. //
  5. // Created by Justin Spahr-Summers on 2012-09-11.
  6. // Copyright (c) 2012 GitHub. All rights reserved.
  7. //
  8. #import "NSError+AWSMTLModelException.h"
  9. #import "AWSMTLModel.h"
  10. #import "AWSEXTRuntimeExtensions.h"
  11. #import "AWSEXTScope.h"
  12. #import "AWSMTLReflection.h"
  13. #import <objc/runtime.h>
  14. // This coupling is needed for backwards compatibility in MTLModel's deprecated
  15. // methods.
  16. #import "AWSMTLJSONAdapter.h"
  17. #import "AWSMTLModel+NSCoding.h"
  18. // Used to cache the reflection performed in +propertyKeys.
  19. static void *MTLModelCachedPropertyKeysKey = &MTLModelCachedPropertyKeysKey;
  20. // Validates a value for an object and sets it if necessary.
  21. //
  22. // obj - The object for which the value is being validated. This value
  23. // must not be nil.
  24. // key - The name of one of `obj`s properties. This value must not be
  25. // nil.
  26. // value - The new value for the property identified by `key`.
  27. // forceUpdate - If set to `YES`, the value is being updated even if validating
  28. // it did not change it.
  29. // error - If not NULL, this may be set to any error that occurs during
  30. // validation
  31. //
  32. // Returns YES if `value` could be validated and set, or NO if an error
  33. // occurred.
  34. static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUpdate, NSError **error) {
  35. // Mark this as being autoreleased, because validateValue may return
  36. // a new object to be stored in this variable (and we don't want ARC to
  37. // double-free or leak the old or new values).
  38. __autoreleasing id validatedValue = value;
  39. @try {
  40. if (![obj validateValue:&validatedValue forKey:key error:error]) return NO;
  41. if (forceUpdate || value != validatedValue) {
  42. [obj setValue:validatedValue forKey:key];
  43. }
  44. return YES;
  45. } @catch (NSException *ex) {
  46. NSLog(@"*** Caught exception setting key \"%@\" : %@", key, ex);
  47. // Fail fast in Debug builds.
  48. #if DEBUG
  49. @throw ex;
  50. #else
  51. if (error != NULL) {
  52. *error = [NSError awsmtl_modelErrorWithException:ex];
  53. }
  54. return NO;
  55. #endif
  56. }
  57. }
  58. @interface AWSMTLModel ()
  59. // Enumerates all properties of the receiver's class hierarchy, starting at the
  60. // receiver, and continuing up until (but not including) MTLModel.
  61. //
  62. // The given block will be invoked multiple times for any properties declared on
  63. // multiple classes in the hierarchy.
  64. + (void)enumeratePropertiesUsingBlock:(void (^)(objc_property_t property, BOOL *stop))block;
  65. @end
  66. @implementation AWSMTLModel
  67. #pragma mark Lifecycle
  68. + (instancetype)modelWithDictionary:(NSDictionary *)dictionary error:(NSError **)error {
  69. return [[self alloc] initWithDictionary:dictionary error:error];
  70. }
  71. - (instancetype)init {
  72. // Nothing special by default, but we have a declaration in the header.
  73. return [super init];
  74. }
  75. - (instancetype)initWithDictionary:(NSDictionary *)dictionary error:(NSError **)error {
  76. self = [self init];
  77. if (self == nil) return nil;
  78. for (NSString *key in dictionary) {
  79. // Mark this as being autoreleased, because validateValue may return
  80. // a new object to be stored in this variable (and we don't want ARC to
  81. // double-free or leak the old or new values).
  82. __autoreleasing id value = [dictionary objectForKey:key];
  83. if ([value isEqual:NSNull.null]) value = nil;
  84. BOOL success = MTLValidateAndSetValue(self, key, value, YES, error);
  85. if (!success) return nil;
  86. }
  87. return self;
  88. }
  89. #pragma mark Reflection
  90. + (void)enumeratePropertiesUsingBlock:(void (^)(objc_property_t property, BOOL *stop))block {
  91. Class cls = self;
  92. BOOL stop = NO;
  93. while (!stop && ![cls isEqual:AWSMTLModel.class]) {
  94. unsigned count = 0;
  95. objc_property_t *properties = class_copyPropertyList(cls, &count);
  96. cls = cls.superclass;
  97. if (properties == NULL) continue;
  98. @onExit {
  99. free(properties);
  100. };
  101. for (unsigned i = 0; i < count; i++) {
  102. block(properties[i], &stop);
  103. if (stop) break;
  104. }
  105. }
  106. }
  107. + (NSSet *)propertyKeys {
  108. NSSet *cachedKeys = objc_getAssociatedObject(self, MTLModelCachedPropertyKeysKey);
  109. if (cachedKeys != nil) return cachedKeys;
  110. NSMutableSet *keys = [NSMutableSet set];
  111. [self enumeratePropertiesUsingBlock:^(objc_property_t property, BOOL *stop) {
  112. awsmtl_propertyAttributes *attributes = awsmtl_copyPropertyAttributes(property);
  113. @onExit {
  114. free(attributes);
  115. };
  116. if (attributes->readonly && attributes->ivar == NULL) return;
  117. NSString *key = @(property_getName(property));
  118. [keys addObject:key];
  119. }];
  120. // It doesn't really matter if we replace another thread's work, since we do
  121. // it atomically and the result should be the same.
  122. objc_setAssociatedObject(self, MTLModelCachedPropertyKeysKey, keys, OBJC_ASSOCIATION_COPY);
  123. return keys;
  124. }
  125. - (NSDictionary *)dictionaryValue {
  126. return [self dictionaryWithValuesForKeys:self.class.propertyKeys.allObjects];
  127. }
  128. #pragma mark Merging
  129. - (void)mergeValueForKey:(NSString *)key fromModel:(AWSMTLModel *)model {
  130. NSParameterAssert(key != nil);
  131. SEL selector = AWSMTLSelectorWithCapitalizedKeyPattern("merge", key, "FromModel:");
  132. if (![self respondsToSelector:selector]) {
  133. if (model != nil) {
  134. [self setValue:[model valueForKey:key] forKey:key];
  135. }
  136. return;
  137. }
  138. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]];
  139. invocation.target = self;
  140. invocation.selector = selector;
  141. [invocation setArgument:&model atIndex:2];
  142. [invocation invoke];
  143. }
  144. - (void)mergeValuesForKeysFromModel:(AWSMTLModel *)model {
  145. NSSet *propertyKeys = model.class.propertyKeys;
  146. for (NSString *key in self.class.propertyKeys) {
  147. if (![propertyKeys containsObject:key]) continue;
  148. [self mergeValueForKey:key fromModel:model];
  149. }
  150. }
  151. #pragma mark Validation
  152. - (BOOL)validate:(NSError **)error {
  153. for (NSString *key in self.class.propertyKeys) {
  154. id value = [self valueForKey:key];
  155. BOOL success = MTLValidateAndSetValue(self, key, value, NO, error);
  156. if (!success) return NO;
  157. }
  158. return YES;
  159. }
  160. #pragma mark NSCopying
  161. - (instancetype)copyWithZone:(NSZone *)zone {
  162. return [[self.class allocWithZone:zone] initWithDictionary:self.dictionaryValue error:NULL];
  163. }
  164. #pragma mark NSObject
  165. - (NSString *)description {
  166. return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.dictionaryValue];
  167. }
  168. - (NSUInteger)hash {
  169. NSUInteger value = 0;
  170. for (NSString *key in self.class.propertyKeys) {
  171. value ^= [[self valueForKey:key] hash];
  172. }
  173. return value;
  174. }
  175. - (BOOL)isEqual:(AWSMTLModel *)model {
  176. if (self == model) return YES;
  177. if (![model isMemberOfClass:self.class]) return NO;
  178. for (NSString *key in self.class.propertyKeys) {
  179. id selfValue = [self valueForKey:key];
  180. id modelValue = [model valueForKey:key];
  181. BOOL valuesEqual = ((selfValue == nil && modelValue == nil) || [selfValue isEqual:modelValue]);
  182. if (!valuesEqual) return NO;
  183. }
  184. return YES;
  185. }
  186. @end