123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989 |
- //
- // NSObject+Motis.m
- // Copyright 2014 Mobile Jazz
- //
- // 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 "NSObject+Motis.h"
- #import <objc/runtime.h>
- #define MOTIS_DEBUG 0 // <-- set 1 to get debug logs
- #if MOTIS_DEBUG
- #define MJLog(format, ...) NSLog(@"%@",[NSString stringWithFormat:format, ## __VA_ARGS__]);
- #else
- #define MJLog(format, ...)
- #endif
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- static NSString* stringFromClass(Class theClass)
- {
- static NSMapTable *map = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- map = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsWeakMemory valueOptions:NSPointerFunctionsStrongMemory];
- });
-
- NSString *string = [map objectForKey:theClass];
-
- if (!string)
- {
- string = NSStringFromClass(theClass);
- [map setObject:string forKey:theClass];
- }
-
- return string;
- }
- static Class classFromString(NSString *string)
- {
- static NSMapTable *map = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- map = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory];
- });
-
- Class theClass = [map objectForKey:string];
-
- if (!theClass)
- {
- theClass = NSClassFromString(string);
- [map setObject:theClass forKey:string];
- }
-
- return theClass;
- }
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- #pragma mark - Motis_Private
- /**
- * Stores a KeyPath and its path components.
- **/
- @interface MTSKeyPath : NSObject
- /**
- * Default initializer
- **/
- - (id)initWithKeyPath:(NSString*)keyPath;
- /**
- * The key (or KeyPath).
- **/
- @property (nonatomic, strong, readonly) NSString *key;
- /**
- * The KeyPath components.
- **/
- @property (nonatomic, strong, readonly) NSArray *components;
- @end
- @implementation MTSKeyPath
- - (id)initWithKeyPath:(NSString*)keyPath
- {
- self = [super init];
- if (self)
- {
- _key = keyPath;
- _components = [keyPath componentsSeparatedByString:@"."];
- }
- return self;
- }
- @end
- #pragma mark -
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- @interface NSObject (Motis_Private)
- /** ---------------------------------------------- **
- * @name Mappings
- ** ---------------------------------------------- **/
- /**
- * Collects all the mappings from each subclass layer.
- * @return The Motis Object Mapping.
- **/
- + (NSDictionary*)mts_cachedMapping;
- /**
- * Collects all the array class mappings from each subclass layer.
- * @return The Motis Array Class Mapping.
- **/
- + (NSDictionary*)mts_cachedArrayClassMapping;
- /**
- * KVC KeyPath support.
- * @return Dictionary of keyPaths.
- * @discussion This method is grouping mts_mapping keys per each "first key path key". For example: {"keyA":["keyA.key1.key2", "keyA.key3", "keyA.key4.key5", ...], "keyB":["keyB.key6.key7", "keyB.key8", ...], ...}. Each array object is a mts_mapping key.
- **/
- + (NSDictionary*)mts_keyPaths;
- /** ---------------------------------------------- **
- * @name Object Class Introspection
- ** ---------------------------------------------- **/
- /**
- * Returns the attribute type string for the given key.
- * @param key The name of property.
- * @return The attribute type string.
- */
- - (NSString*)mts_typeAttributeForKey:(NSString*)key;
- /**
- * YES if the attribute type can be converted into a Class object, NO otherwise.
- * @param typeAttribute The value returned by `-mts_typeAttributeForKey:`.
- * @return YES if it represents an object (therefore, exists a related class object).
- */
- - (BOOL)mts_isClassTypeTypeAttribute:(NSString*)typeAttribute;
- /**
- * Retrieve the class name and the array of protocols that the property implements.
- * @param className The returning class name.
- * @param protocols The returning array of protocols.
- * @param typeAttribute The value returned by `-mts_typeAttributeForKey:`.
- */
- - (void)mts_getClassName:(out NSString *__autoreleasing*)className protocols:(out NSArray *__autoreleasing*)protocols fromTypeAttribute:(NSString*)typeAttribute;
- /** ---------------------------------------------- **
- * @name Automatic Validation
- ** ---------------------------------------------- **/
- /**
- * Return YES if the value has been automatically validated. The newer value is setted in the pointer.
- * @param ioValue The value to be validated.
- * @param key The property key in which the validated value is going to be assigned.
- * @return YES if automatic validation has been done, NO otherwise.
- * @discussion A return value of NO only indicates that the value couldn't be validated automatically.
- **/
- - (BOOL)mts_validateAutomaticallyValue:(inout __autoreleasing id *)ioValue forKey:(NSString*)key;
- /**
- * Return YES if the value has been automatically validated. The newer value is setted in the pointer.
- * @param ioValue The value to be validated.
- * @param typeClass The final class for the value.
- * @param key The property key in which the validated value will be assigned, either directly or as part of an array.
- * @return YES if automatic validation has been done, NO otherwise.
- * @discussion A return value of NO only indicates that the value couldn't be validated automatically.
- **/
- - (BOOL)mts_validateAutomaticallyValue:(inout __autoreleasing id *)ioValue toClass:(Class)typeClass forKey:(NSString*)key;
- @end
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- #pragma mark - Motis
- @implementation NSObject (Motis)
- #pragma mark Public Methods
- - (void)mts_setValue:(id)value forKey:(NSString *)key
- {
- NSString *mappedKey = [self mts_mapKey:key];
-
- if (!mappedKey)
- {
- [self mts_ignoredSetValue:value forUndefinedMappingKey:key];
- return;
- }
-
- if (value == [NSNull null])
- value = nil;
-
- if ([value isKindOfClass:NSArray.class])
- {
- __block NSMutableArray *modifiedArray = nil;
-
- [value enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id object, NSUInteger idx, BOOL *stop) {
-
- id validatedObject = object;
-
- NSError *error = nil;
- BOOL validated = [self mts_validateArrayObject:&validatedObject forArrayKey:mappedKey error:&error];
-
- // Automatic validation only if the value has not been manually validated
- if (object == validatedObject && validated)
- {
- Class typeClass = [self.class mts_cachedArrayClassMapping][mappedKey];
- if (typeClass)
- validated = [self mts_validateAutomaticallyValue:&validatedObject toClass:typeClass forKey:mappedKey];
- }
-
- if (validated)
- {
- if (validatedObject != object)
- {
- if (!modifiedArray)
- modifiedArray = [value mutableCopy];
-
- modifiedArray[idx] = validatedObject;
- }
- }
- else
- {
- if (!modifiedArray)
- modifiedArray = [value mutableCopy];
-
- [modifiedArray removeObjectAtIndex:idx];
-
- [self mts_invalidValue:validatedObject forArrayKey:mappedKey error:error];
- }
- }];
-
- if (modifiedArray)
- value = modifiedArray;
- }
-
- id originalValue = value;
-
- NSError *error = nil;
- BOOL validated = [self mts_validateValue:&value forKey:mappedKey error:&error];
-
- // Automatic validation only if the value has not been manually validated
- if (originalValue == value && validated)
- validated = [self mts_validateAutomaticallyValue:&value forKey:mappedKey];
-
- if (validated)
- {
- [self setValue:value forKey:mappedKey];
- }
- else
- [self mts_invalidValue:value forKey:mappedKey error:error];
- }
- - (void)mts_setValuesForKeysWithDictionary:(NSDictionary *)keyedValues
- {
- for (NSString *key in keyedValues)
- {
- // For each key-value pair in the dictionary
-
- NSArray *allKeyPath = [self.class mts_keyPaths][key];
-
- BOOL attemptedToSetOriginalKey = NO; // <-- YES when value for "key" has been set
-
- for (MTSKeyPath *keyPath in allKeyPath)
- {
- // For each key related keyPath
-
- BOOL validKeyPath = NO;
- id value = keyedValues;
-
- NSUInteger count = keyPath.components.count;
-
- for (NSInteger i=0; i<count; ++i)
- {
- // For each keyPath component
-
- NSString *path = keyPath.components[i];
- value = [value valueForKey:path];
-
- if (i < count - 1)
- {
- if (![value isKindOfClass:NSDictionary.class]) // <-- Checking that the KeyPath is only accessing dictionary objects.
- break;
- }
- else
- {
- validKeyPath = YES;
- }
- }
-
- if (value && validKeyPath)
- {
- if (!attemptedToSetOriginalKey && [keyPath.key isEqualToString:key])
- attemptedToSetOriginalKey = YES;
-
- [self mts_setValue:value forKey:keyPath.key];
- }
- }
-
- // If "key" has not been listed in Motis available keyPaths list,
- // lets perform the setter of the value for the given key.
- if (!attemptedToSetOriginalKey)
- [self mts_setValue:keyedValues[key] forKey:key];
- }
- }
- - (id)mts_valueForKey:(NSString*)key
- {
- NSString *mappedKey = [self mts_mapKey:key];
-
- if (!mappedKey)
- return nil;
-
- id value = [self valueForKey:mappedKey];
-
- return value;
- }
- - (NSDictionary*)mts_dictionaryWithValuesForKeys:(NSArray *)keys
- {
- NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:keys.count];
-
- for (NSString *key in keys)
- {
- id value = [self mts_valueForKey:key];
- if (!value)
- value = [NSNull null];
-
- dictionary[key] = value;
- }
-
- return [dictionary copy];
- }
- - (NSString*)mts_extendedObjectDescription
- {
- NSString *description = self.description;
- NSArray *keys = [[self.class mts_cachedMapping] allValues];
- if (keys.count > 0)
- {
- NSDictionary *keyValues = [self dictionaryWithValuesForKeys:keys];
- return [NSString stringWithFormat:@"%@ - Mapped Values: %@", description, [keyValues description]];
- }
- return description;
- }
- #pragma mark Private Methods
- - (NSString*)mts_mapKey:(NSString*)key
- {
- NSDictionary *mapping = [self.class mts_cachedMapping];
- NSString *mappedKey = mapping[key];
-
- if (mappedKey)
- return mappedKey;
-
- if ([self.class mts_shouldSetUndefinedKeys])
- return key;
-
- return nil;
- }
- @end
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- #pragma mark - Motis_Subclassing
- @implementation NSObject (Motis_Subclassing)
- + (NSDictionary*)mts_mapping
- {
- // Subclasses must override, always adding super to the mapping!
- return @{};
- }
- + (BOOL)mts_shouldSetUndefinedKeys
- {
- // Subclasses might override.
- NSDictionary *mapping = [self mts_cachedMapping];
- return mapping.count == 0;
- }
- + (NSDictionary*)mts_arrayClassMapping
- {
- // Subclasses might override.
- return @{};
- }
- - (id)mts_willCreateObjectOfClass:(Class)typeClass withDictionary:(NSDictionary*)dictionary forKey:(NSString*)key abort:(BOOL*)abort;
- {
- // Subclasses might override.
- return nil;
- }
- - (void)mts_didCreateObject:(id)object forKey:(NSString *)key
- {
- // Subclasses might override.
- }
- + (NSDateFormatter*)mts_validationDateFormatter
- {
- // Subclasses may override and return a custom formatter.
- return nil;
- }
- - (BOOL)mts_validateValue:(inout __autoreleasing id *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing *)outError
- {
- // Subclasses might override.
- return [self validateValue:ioValue forKey:inKey error:outError];
- }
- - (BOOL)mts_validateArrayObject:(inout __autoreleasing id *)ioValue forArrayKey:(NSString *)arrayKey error:(out NSError *__autoreleasing *)outError
- {
- // Subclasses might override.
- return YES;
- }
- - (void)mts_ignoredSetValue:(id)value forUndefinedMappingKey:(NSString*)key
- {
- // Subclasses might override.
- MJLog(@"Undefined Mapping Key <%@> in class %@.", key, [self.class description]);
- }
- - (void)mts_invalidValue:(id)value forKey:(NSString *)key error:(NSError*)error
- {
- // Subclasses might override.
- MJLog(@"Value for Key <%@> is not valid in class %@. Error: %@", key, [self.class description], error);
- }
- - (void)mts_invalidValue:(id)value forArrayKey:(NSString *)key error:(NSError*)error
- {
- // Subclasses might override.
- MJLog(@"Item for ArrayKey <%@> is not valid in class %@. Error: %@", key, [self.class description], error);
- }
- @end
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
- #pragma mark - Motis_Private
- @implementation NSObject (Motis_Private)
- __attribute__((constructor))
- static void mts_motisInitialization()
- {
- [NSObject mts_cachedMapping];
- [NSObject mts_cachedArrayClassMapping];
- [NSObject mts_keyPaths];
- [NSObject mts_decimalFormatterAllowFloats];
- [NSObject mts_decimalFormatterNoFloats];
- }
- + (NSDictionary*)mts_cachedMapping
- {
- static NSMutableDictionary *mappings = nil;
-
- static dispatch_once_t onceToken1;
- dispatch_once(&onceToken1, ^{
- mappings = [NSMutableDictionary dictionary];
- });
-
- NSString *className = stringFromClass(self);
- NSDictionary *mapping = mappings[className];
-
- if (!mapping)
- {
- Class superClass = [self superclass];
-
- NSMutableDictionary *dictionary = nil;
-
- if ([superClass isSubclassOfClass:NSObject.class])
- dictionary = [[superClass mts_cachedMapping] mutableCopy];
- else
- dictionary = [NSMutableDictionary dictionary];
-
- [dictionary addEntriesFromDictionary:[self mts_mapping]];
-
- mapping = [dictionary copy];
- mappings[className] = mapping;
- }
- return mapping;
- }
- + (NSDictionary*)mts_cachedArrayClassMapping
- {
- static NSMutableDictionary *arrayClassMappings = nil;
-
- static dispatch_once_t onceToken1;
- dispatch_once(&onceToken1, ^{
- arrayClassMappings = [NSMutableDictionary dictionary];
- });
-
- NSString *className = stringFromClass(self);
- NSDictionary *arrayClassMapping = arrayClassMappings[className];
-
- if (!arrayClassMapping)
- {
- Class superClass = [self superclass];
-
- NSMutableDictionary *mapping = nil;
-
- if ([superClass isSubclassOfClass:NSObject.class])
- mapping = [[superClass mts_cachedArrayClassMapping] mutableCopy];
- else
- mapping = [NSMutableDictionary dictionary];
-
- [mapping addEntriesFromDictionary:[self mts_arrayClassMapping]];
-
- arrayClassMapping = [mapping copy];
- arrayClassMappings[className] = arrayClassMapping;
- }
-
- return arrayClassMapping;
- }
- + (NSDictionary*)mts_keyPaths
- {
- static NSMutableDictionary *classKeyPaths = nil;
-
- static dispatch_once_t onceToken1;
- dispatch_once(&onceToken1, ^{
- classKeyPaths = [NSMutableDictionary dictionary];
- });
-
- NSString *className = stringFromClass(self);
- NSDictionary *keyPaths = classKeyPaths[className];
-
- if (!keyPaths)
- {
- Class superClass = [self superclass];
-
- NSMutableDictionary *dict = nil;
-
- if ([superClass isSubclassOfClass:NSObject.class])
- dict = [[superClass mts_keyPaths] mutableCopy];
- else
- dict = [NSMutableDictionary dictionary];
-
- NSDictionary *mapping = [self mts_mapping];
- for (NSString *key in mapping)
- {
- MTSKeyPath *keyPath = [[MTSKeyPath alloc] initWithKeyPath:key];
- NSString *firstPath = keyPath.components[0];
-
- NSMutableArray *listOfKeyPaths = dict[firstPath];
- if (!listOfKeyPaths)
- {
- listOfKeyPaths = [NSMutableArray array];
- dict[firstPath] = listOfKeyPaths;
- }
-
- [listOfKeyPaths addObject:keyPath];
- }
-
- keyPaths = [dict copy];
- classKeyPaths[className] = keyPaths;
- }
-
- return keyPaths;
- }
- - (NSString*)mts_typeAttributeForKey:(NSString*)key
- {
- static NSMutableDictionary *typeAttributes = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- typeAttributes = [NSMutableDictionary dictionary];
- });
-
- NSMutableDictionary *classTypeAttributes = typeAttributes[stringFromClass(self.class)];
- if (!classTypeAttributes)
- {
- classTypeAttributes = [NSMutableDictionary dictionary];
- typeAttributes[stringFromClass(self.class)] = classTypeAttributes;
- }
-
- NSString *typeAttribute = classTypeAttributes[key];
- if (typeAttribute)
- return typeAttribute;
-
- objc_property_t property = class_getProperty(self.class, key.UTF8String);
-
- if (!property)
- return nil;
-
- const char * type = property_getAttributes(property);
-
- NSString * typeString = @(type);
- NSArray * attributes = [typeString componentsSeparatedByString:@","];
- typeAttribute = attributes[0];
-
- classTypeAttributes[key] = typeAttribute;
-
- return typeAttribute;
- }
- - (BOOL)mts_isClassTypeTypeAttribute:(NSString*)typeAttribute
- {
- static NSMutableDictionary *dictionary = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- dictionary = [NSMutableDictionary dictionary];
- });
-
- NSNumber *isClassType = dictionary[typeAttribute];
- if (!isClassType)
- {
- isClassType = @([typeAttribute hasPrefix:@"T@"] && ([typeAttribute length] > 1));
- dictionary[typeAttribute] = isClassType;
- }
-
- return isClassType.boolValue;
- }
- - (void)mts_getClassName:(out NSString *__autoreleasing*)className protocols:(out NSArray *__autoreleasing*)protocols fromTypeAttribute:(NSString*)typeAttribute
- {
- static NSMutableDictionary *dictionary = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- dictionary = [NSMutableDictionary dictionary];
- });
-
- NSArray *array = dictionary[typeAttribute];
- if (array)
- {
- if (array.count > 0)
- {
- *className = array[0];
- *protocols = array[1];
- }
- return;
- }
-
- if ([self mts_isClassTypeTypeAttribute:typeAttribute])
- {
- if (typeAttribute.length < 3)
- {
- *className = @"";
- return;
- }
-
- NSString *classAttribute = [typeAttribute substringWithRange:NSMakeRange(3, typeAttribute.length-4)];
-
- NSString *protocolNames = nil;
-
- if (classAttribute)
- {
- NSScanner *scanner = [NSScanner scannerWithString:classAttribute];
- [scanner scanUpToString:@"<" intoString:className];
- [scanner scanUpToString:@">" intoString:&protocolNames];
- }
-
- if (*className == nil)
- *className = @"";
-
- if (protocolNames.length > 0)
- {
- protocolNames = [protocolNames substringFromIndex:1];
- protocolNames = [protocolNames stringByReplacingOccurrencesOfString:@" " withString:@""];
- *protocols = [protocolNames componentsSeparatedByString:@","];
- }
- else
- *protocols = @[];
-
- NSArray *array = @[*className, *protocols];
- dictionary[typeAttribute] = array;
- }
- else
- {
- dictionary[typeAttribute] = @[];
- }
- }
- - (BOOL)mts_validateAutomaticallyValue:(inout __autoreleasing id *)ioValue forKey:(NSString*)key
- {
- if (*ioValue == nil)
- return YES;
-
- NSString * typeAttribute = [self mts_typeAttributeForKey:key];
-
- if (!typeAttribute) // <-- If no attribute, abort automatic validation
- return YES;
-
- NSString * propertyType = [typeAttribute substringFromIndex:1];
- const char * rawPropertyType = [propertyType UTF8String];
-
- if ([self mts_isClassTypeTypeAttribute:typeAttribute])
- {
- NSString *className = nil;
- NSArray *protocols = nil;
-
- [self mts_getClassName:&className protocols:&protocols fromTypeAttribute:typeAttribute];
-
- if (className.length == 0)
- {
- // It's an "id".
-
- // Actually, we should make a compare like this:
- // if (strcmp(rawPropertyType, @encode(id)) == 0)
- // return YES;
- //
- // However, becuase of the "if" statements, we know that our typeAttribute begins with a "@" and
- // if "className.length" == 0 means that the "rawPropertyType" to be compared is exactly an @encode(id).
- //
- // Therefore, we return directly YES.
-
- return YES;
- }
- else
- {
- Class typeClass = classFromString(className);
-
- if (typeClass)
- {
- MJLog(@"%@ --> %@", key, stringFromClass(typeClass));
- return [self mts_validateAutomaticallyValue:ioValue toClass:typeClass forKey:key];
- }
- }
-
- return NO;
- }
- else // because it is not a class, the property must be a basic type
- {
- if ([*ioValue isKindOfClass:NSNumber.class])
- {
- #if defined(__LP64__) && __LP64__
- // Nothing to do
- #else
- if (strcmp(rawPropertyType, @encode(BOOL)) == 0)
- {
- *ioValue = @([*ioValue boolValue]);
- return *ioValue != nil;
- }
- #endif
- // Conversion from NSNumber to basic types is already handled by the system.
- return YES;
- }
- else if ([*ioValue isKindOfClass:NSString.class])
- {
- if (strcmp(rawPropertyType, @encode(BOOL)) == 0)
- {
- if ([*ioValue isKindOfClass:NSString.class])
- {
- if ([*ioValue compare:@"true" options:NSCaseInsensitiveSearch] == NSOrderedSame)
- *ioValue = @YES;
- else if ([*ioValue compare:@"false" options:NSCaseInsensitiveSearch] == NSOrderedSame)
- *ioValue = @NO;
- else
- *ioValue = @([*ioValue doubleValue] != 0.0);
-
- return *ioValue != nil;
- }
- }
- else if (strcmp(rawPropertyType, @encode(unsigned long long)) == 0)
- {
- if ([*ioValue isKindOfClass:NSString.class])
- {
- *ioValue = [[self.class mts_decimalFormatterNoFloats] numberFromString:*ioValue];
- return *ioValue != nil;
- }
- }
- return YES;
- }
- else // If not a number and not a string, types cannot match.
- {
- return NO;
- }
- }
- }
- - (BOOL)mts_validateAutomaticallyValue:(inout __autoreleasing id *)ioValue toClass:(Class)typeClass forKey:(NSString*)key
- {
- // If types match, just return
- if ([*ioValue isKindOfClass:typeClass])
- return YES;
-
- // Otherwise, lets try to fit the desired class type
- // Because *ioValue comes frome a JSON deserialization, it can only be a string, number, array or dictionary.
-
- if ([*ioValue isKindOfClass:NSString.class]) // <-- STRINGS
- {
- if ([typeClass isSubclassOfClass:NSURL.class])
- {
- *ioValue = [NSURL URLWithString:*ioValue];
- return *ioValue != nil;
- }
- else if ([typeClass isSubclassOfClass:NSData.class])
- {
- *ioValue = [[NSData alloc] initWithBase64EncodedString:*ioValue options:NSDataBase64DecodingIgnoreUnknownCharacters];
- return *ioValue != nil;
- }
- else if ([typeClass isSubclassOfClass:NSNumber.class])
- {
- if ([*ioValue compare:@"true" options:NSCaseInsensitiveSearch] == NSOrderedSame)
- *ioValue = @YES;
- else if ([*ioValue compare:@"false" options:NSCaseInsensitiveSearch] == NSOrderedSame)
- *ioValue = @NO;
- else
- *ioValue = @([*ioValue doubleValue]);
-
- return *ioValue != nil;
-
- // Using NSNumberFormatter is slower!
- // NSNumberFormatter *formatter = [NSObject mts_decimalFormatterAllowFloats];
- // *ioValue = [formatter numberFromString:*ioValue];
- }
- if ([typeClass isSubclassOfClass:NSDate.class])
- {
- NSDateFormatter *dateFormatter = [self.class mts_validationDateFormatter];
- if (dateFormatter)
- {
- *ioValue = [dateFormatter dateFromString:*ioValue];
- return *ioValue != nil;
- }
- else
- {
- static NSCharacterSet *characterSet = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- characterSet = [NSCharacterSet characterSetWithCharactersInString:@"1234567890.,"];
- });
-
- NSString *trimmedString = [*ioValue stringByTrimmingCharactersInSet:characterSet];
- if (trimmedString.length == 0)
- {
- NSTimeInterval timeInterval = [*ioValue doubleValue];
- *ioValue = [NSDate dateWithTimeIntervalSince1970:timeInterval];
- return *ioValue != nil;
- }
-
- // Another approach using REGEX to validate the number (slower):
-
- // static NSRegularExpression *regex = nil;
- // static dispatch_once_t onceToken;
- // dispatch_once(&onceToken, ^{
- // regex = [[NSRegularExpression alloc] initWithPattern:@"\\d*[.]?\\d+"
- // options:NSRegularExpressionCaseInsensitive
- // error:nil];
- // });
- //
- // NSTextCheckingResult *match = [regex firstMatchInString:*ioValue options:0 range:NSMakeRange(0, [*ioValue length])];
- // if (match && match.range.location == 0 && match.range.length == [*ioValue length])
- // {
- // NSTimeInterval timeInterval = [*ioValue doubleValue];
- // *ioValue = [NSDate dateWithTimeIntervalSince1970:timeInterval];
- // return *ioValue != nil;
- // }
-
- }
- }
- }
- else if ([*ioValue isKindOfClass:NSNumber.class]) // <-- NUMBERS
- {
- if ([typeClass isSubclassOfClass:NSDate.class])
- {
- *ioValue = [NSDate dateWithTimeIntervalSince1970:[*ioValue doubleValue]];
- return *ioValue != nil;
- }
- else if ([typeClass isSubclassOfClass:NSString.class])
- {
- *ioValue = [*ioValue stringValue];
- return *ioValue != nil;
- }
- }
- else if ([*ioValue isKindOfClass:NSArray.class]) // <-- ARRAYS
- {
- if ([typeClass isSubclassOfClass:NSMutableArray.class])
- {
- *ioValue = [NSMutableArray arrayWithArray:*ioValue];
- return *ioValue != nil;
- }
- else if ([typeClass isSubclassOfClass:NSMutableSet.class])
- {
- *ioValue = [NSMutableSet setWithArray:*ioValue];
- return *ioValue != nil;
- }
- else if ([typeClass isSubclassOfClass:NSSet.class])
- {
- *ioValue = [NSSet setWithArray:*ioValue];
- return *ioValue != nil;
- }
- else if ([typeClass isSubclassOfClass:NSMutableOrderedSet.class])
- {
- *ioValue = [NSMutableOrderedSet orderedSetWithArray:*ioValue];
- return *ioValue != nil;
- }
- else if ([typeClass isSubclassOfClass:NSOrderedSet.class])
- {
- *ioValue = [NSOrderedSet orderedSetWithArray:*ioValue];
- return *ioValue != nil;
- }
- }
- else if ([*ioValue isKindOfClass:NSDictionary.class]) // <-- DICTIONARIES
- {
- if ([typeClass isSubclassOfClass:NSMutableDictionary.class])
- {
- *ioValue = [NSMutableDictionary dictionaryWithDictionary:*ioValue];
- return *ioValue != nil;
- }
- else if ([typeClass isSubclassOfClass:NSObject.class])
- {
- BOOL abort = NO;
- id instance = [self mts_willCreateObjectOfClass:typeClass withDictionary:*ioValue forKey:key abort:&abort];
-
- if (abort)
- return NO;
-
- if (!instance)
- {
- instance = [[typeClass alloc] init];
- [instance mts_setValuesForKeysWithDictionary:*ioValue];
- }
-
- [self mts_didCreateObject:instance forKey:key];
-
- *ioValue = instance;
- return *ioValue != nil;
- }
- }
-
- return NO;
- }
- #pragma mark Private Helpers
- + (NSNumberFormatter*)mts_decimalFormatterAllowFloats
- {
- static NSNumberFormatter *decimalFormatter = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- decimalFormatter = [NSNumberFormatter new];
- decimalFormatter.numberStyle = NSNumberFormatterDecimalStyle;
- decimalFormatter.allowsFloats = YES;
- });
- return decimalFormatter;
- }
- + (NSNumberFormatter*)mts_decimalFormatterNoFloats
- {
- static NSNumberFormatter *decimalFormatter = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- decimalFormatter = [NSNumberFormatter new];
- decimalFormatter.numberStyle = NSNumberFormatterDecimalStyle;
- decimalFormatter.allowsFloats = NO;
- });
- return decimalFormatter;
- }
- @end
|