123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- //
- // MTLModel.m
- // Mantle
- //
- // Created by Justin Spahr-Summers on 2012-09-11.
- // Copyright (c) 2012 GitHub. All rights reserved.
- //
-
- #import "NSError+AWSMTLModelException.h"
- #import "AWSMTLModel.h"
- #import "AWSEXTRuntimeExtensions.h"
- #import "AWSEXTScope.h"
- #import "AWSMTLReflection.h"
- #import <objc/runtime.h>
-
- // This coupling is needed for backwards compatibility in MTLModel's deprecated
- // methods.
- #import "AWSMTLJSONAdapter.h"
- #import "AWSMTLModel+NSCoding.h"
-
- // Used to cache the reflection performed in +propertyKeys.
- static void *MTLModelCachedPropertyKeysKey = &MTLModelCachedPropertyKeysKey;
-
- // Validates a value for an object and sets it if necessary.
- //
- // obj - The object for which the value is being validated. This value
- // must not be nil.
- // key - The name of one of `obj`s properties. This value must not be
- // nil.
- // value - The new value for the property identified by `key`.
- // forceUpdate - If set to `YES`, the value is being updated even if validating
- // it did not change it.
- // error - If not NULL, this may be set to any error that occurs during
- // validation
- //
- // Returns YES if `value` could be validated and set, or NO if an error
- // occurred.
- static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUpdate, NSError **error) {
- // 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 validatedValue = value;
-
- @try {
- if (![obj validateValue:&validatedValue forKey:key error:error]) return NO;
-
- if (forceUpdate || value != validatedValue) {
- [obj setValue:validatedValue forKey:key];
- }
-
- return YES;
- } @catch (NSException *ex) {
- NSLog(@"*** Caught exception setting key \"%@\" : %@", key, ex);
-
- // Fail fast in Debug builds.
- #if DEBUG
- @throw ex;
- #else
- if (error != NULL) {
- *error = [NSError awsmtl_modelErrorWithException:ex];
- }
-
- return NO;
- #endif
- }
- }
-
- @interface AWSMTLModel ()
-
- // Enumerates all properties of the receiver's class hierarchy, starting at the
- // receiver, and continuing up until (but not including) MTLModel.
- //
- // The given block will be invoked multiple times for any properties declared on
- // multiple classes in the hierarchy.
- + (void)enumeratePropertiesUsingBlock:(void (^)(objc_property_t property, BOOL *stop))block;
-
- @end
-
- @implementation AWSMTLModel
-
- #pragma mark Lifecycle
-
- + (instancetype)modelWithDictionary:(NSDictionary *)dictionary error:(NSError **)error {
- return [[self alloc] initWithDictionary:dictionary error:error];
- }
-
- - (instancetype)init {
- // Nothing special by default, but we have a declaration in the header.
- return [super init];
- }
-
- - (instancetype)initWithDictionary:(NSDictionary *)dictionary error:(NSError **)error {
- self = [self init];
- if (self == nil) return nil;
-
- for (NSString *key in dictionary) {
- // 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 value = [dictionary objectForKey:key];
-
- if ([value isEqual:NSNull.null]) value = nil;
-
- BOOL success = MTLValidateAndSetValue(self, key, value, YES, error);
- if (!success) return nil;
- }
-
- return self;
- }
-
- #pragma mark Reflection
-
- + (void)enumeratePropertiesUsingBlock:(void (^)(objc_property_t property, BOOL *stop))block {
- Class cls = self;
- BOOL stop = NO;
-
- while (!stop && ![cls isEqual:AWSMTLModel.class]) {
- unsigned count = 0;
- objc_property_t *properties = class_copyPropertyList(cls, &count);
-
- cls = cls.superclass;
- if (properties == NULL) continue;
-
- @onExit {
- free(properties);
- };
-
- for (unsigned i = 0; i < count; i++) {
- block(properties[i], &stop);
- if (stop) break;
- }
- }
- }
-
- + (NSSet *)propertyKeys {
- NSSet *cachedKeys = objc_getAssociatedObject(self, MTLModelCachedPropertyKeysKey);
- if (cachedKeys != nil) return cachedKeys;
-
- NSMutableSet *keys = [NSMutableSet set];
-
- [self enumeratePropertiesUsingBlock:^(objc_property_t property, BOOL *stop) {
- awsmtl_propertyAttributes *attributes = awsmtl_copyPropertyAttributes(property);
- @onExit {
- free(attributes);
- };
-
- if (attributes->readonly && attributes->ivar == NULL) return;
-
- NSString *key = @(property_getName(property));
- [keys addObject:key];
- }];
-
- // It doesn't really matter if we replace another thread's work, since we do
- // it atomically and the result should be the same.
- objc_setAssociatedObject(self, MTLModelCachedPropertyKeysKey, keys, OBJC_ASSOCIATION_COPY);
-
- return keys;
- }
-
- - (NSDictionary *)dictionaryValue {
- return [self dictionaryWithValuesForKeys:self.class.propertyKeys.allObjects];
- }
-
- #pragma mark Merging
-
- - (void)mergeValueForKey:(NSString *)key fromModel:(AWSMTLModel *)model {
- NSParameterAssert(key != nil);
-
- SEL selector = AWSMTLSelectorWithCapitalizedKeyPattern("merge", key, "FromModel:");
- if (![self respondsToSelector:selector]) {
- if (model != nil) {
- [self setValue:[model valueForKey:key] forKey:key];
- }
-
- return;
- }
-
- NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]];
- invocation.target = self;
- invocation.selector = selector;
-
- [invocation setArgument:&model atIndex:2];
- [invocation invoke];
- }
-
- - (void)mergeValuesForKeysFromModel:(AWSMTLModel *)model {
- NSSet *propertyKeys = model.class.propertyKeys;
- for (NSString *key in self.class.propertyKeys) {
- if (![propertyKeys containsObject:key]) continue;
-
- [self mergeValueForKey:key fromModel:model];
- }
- }
-
- #pragma mark Validation
-
- - (BOOL)validate:(NSError **)error {
- for (NSString *key in self.class.propertyKeys) {
- id value = [self valueForKey:key];
-
- BOOL success = MTLValidateAndSetValue(self, key, value, NO, error);
- if (!success) return NO;
- }
-
- return YES;
- }
-
- #pragma mark NSCopying
-
- - (instancetype)copyWithZone:(NSZone *)zone {
- return [[self.class allocWithZone:zone] initWithDictionary:self.dictionaryValue error:NULL];
- }
-
- #pragma mark NSObject
-
- - (NSString *)description {
- return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.dictionaryValue];
- }
-
- - (NSUInteger)hash {
- NSUInteger value = 0;
-
- for (NSString *key in self.class.propertyKeys) {
- value ^= [[self valueForKey:key] hash];
- }
-
- return value;
- }
-
- - (BOOL)isEqual:(AWSMTLModel *)model {
- if (self == model) return YES;
- if (![model isMemberOfClass:self.class]) return NO;
-
- for (NSString *key in self.class.propertyKeys) {
- id selfValue = [self valueForKey:key];
- id modelValue = [model valueForKey:key];
-
- BOOL valuesEqual = ((selfValue == nil && modelValue == nil) || [selfValue isEqual:modelValue]);
- if (!valuesEqual) return NO;
- }
-
- return YES;
- }
-
- @end
|