No Description

NSObject+Motis.m 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989
  1. //
  2. // NSObject+Motis.m
  3. // Copyright 2014 Mobile Jazz
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. #import "NSObject+Motis.h"
  17. #import <objc/runtime.h>
  18. #define MOTIS_DEBUG 0 // <-- set 1 to get debug logs
  19. #if MOTIS_DEBUG
  20. #define MJLog(format, ...) NSLog(@"%@",[NSString stringWithFormat:format, ## __VA_ARGS__]);
  21. #else
  22. #define MJLog(format, ...)
  23. #endif
  24. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  25. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  26. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  27. static NSString* stringFromClass(Class theClass)
  28. {
  29. static NSMapTable *map = nil;
  30. static dispatch_once_t onceToken;
  31. dispatch_once(&onceToken, ^{
  32. map = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsWeakMemory valueOptions:NSPointerFunctionsStrongMemory];
  33. });
  34. NSString *string = [map objectForKey:theClass];
  35. if (!string)
  36. {
  37. string = NSStringFromClass(theClass);
  38. [map setObject:string forKey:theClass];
  39. }
  40. return string;
  41. }
  42. static Class classFromString(NSString *string)
  43. {
  44. static NSMapTable *map = nil;
  45. static dispatch_once_t onceToken;
  46. dispatch_once(&onceToken, ^{
  47. map = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory];
  48. });
  49. Class theClass = [map objectForKey:string];
  50. if (!theClass)
  51. {
  52. theClass = NSClassFromString(string);
  53. [map setObject:theClass forKey:string];
  54. }
  55. return theClass;
  56. }
  57. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  58. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  59. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  60. #pragma mark - Motis_Private
  61. /**
  62. * Stores a KeyPath and its path components.
  63. **/
  64. @interface MTSKeyPath : NSObject
  65. /**
  66. * Default initializer
  67. **/
  68. - (id)initWithKeyPath:(NSString*)keyPath;
  69. /**
  70. * The key (or KeyPath).
  71. **/
  72. @property (nonatomic, strong, readonly) NSString *key;
  73. /**
  74. * The KeyPath components.
  75. **/
  76. @property (nonatomic, strong, readonly) NSArray *components;
  77. @end
  78. @implementation MTSKeyPath
  79. - (id)initWithKeyPath:(NSString*)keyPath
  80. {
  81. self = [super init];
  82. if (self)
  83. {
  84. _key = keyPath;
  85. _components = [keyPath componentsSeparatedByString:@"."];
  86. }
  87. return self;
  88. }
  89. @end
  90. #pragma mark -
  91. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  92. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  93. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  94. @interface NSObject (Motis_Private)
  95. /** ---------------------------------------------- **
  96. * @name Mappings
  97. ** ---------------------------------------------- **/
  98. /**
  99. * Collects all the mappings from each subclass layer.
  100. * @return The Motis Object Mapping.
  101. **/
  102. + (NSDictionary*)mts_cachedMapping;
  103. /**
  104. * Collects all the array class mappings from each subclass layer.
  105. * @return The Motis Array Class Mapping.
  106. **/
  107. + (NSDictionary*)mts_cachedArrayClassMapping;
  108. /**
  109. * KVC KeyPath support.
  110. * @return Dictionary of keyPaths.
  111. * @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.
  112. **/
  113. + (NSDictionary*)mts_keyPaths;
  114. /** ---------------------------------------------- **
  115. * @name Object Class Introspection
  116. ** ---------------------------------------------- **/
  117. /**
  118. * Returns the attribute type string for the given key.
  119. * @param key The name of property.
  120. * @return The attribute type string.
  121. */
  122. - (NSString*)mts_typeAttributeForKey:(NSString*)key;
  123. /**
  124. * YES if the attribute type can be converted into a Class object, NO otherwise.
  125. * @param typeAttribute The value returned by `-mts_typeAttributeForKey:`.
  126. * @return YES if it represents an object (therefore, exists a related class object).
  127. */
  128. - (BOOL)mts_isClassTypeTypeAttribute:(NSString*)typeAttribute;
  129. /**
  130. * Retrieve the class name and the array of protocols that the property implements.
  131. * @param className The returning class name.
  132. * @param protocols The returning array of protocols.
  133. * @param typeAttribute The value returned by `-mts_typeAttributeForKey:`.
  134. */
  135. - (void)mts_getClassName:(out NSString *__autoreleasing*)className protocols:(out NSArray *__autoreleasing*)protocols fromTypeAttribute:(NSString*)typeAttribute;
  136. /** ---------------------------------------------- **
  137. * @name Automatic Validation
  138. ** ---------------------------------------------- **/
  139. /**
  140. * Return YES if the value has been automatically validated. The newer value is setted in the pointer.
  141. * @param ioValue The value to be validated.
  142. * @param key The property key in which the validated value is going to be assigned.
  143. * @return YES if automatic validation has been done, NO otherwise.
  144. * @discussion A return value of NO only indicates that the value couldn't be validated automatically.
  145. **/
  146. - (BOOL)mts_validateAutomaticallyValue:(inout __autoreleasing id *)ioValue forKey:(NSString*)key;
  147. /**
  148. * Return YES if the value has been automatically validated. The newer value is setted in the pointer.
  149. * @param ioValue The value to be validated.
  150. * @param typeClass The final class for the value.
  151. * @param key The property key in which the validated value will be assigned, either directly or as part of an array.
  152. * @return YES if automatic validation has been done, NO otherwise.
  153. * @discussion A return value of NO only indicates that the value couldn't be validated automatically.
  154. **/
  155. - (BOOL)mts_validateAutomaticallyValue:(inout __autoreleasing id *)ioValue toClass:(Class)typeClass forKey:(NSString*)key;
  156. @end
  157. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  158. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  159. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  160. #pragma mark - Motis
  161. @implementation NSObject (Motis)
  162. #pragma mark Public Methods
  163. - (void)mts_setValue:(id)value forKey:(NSString *)key
  164. {
  165. NSString *mappedKey = [self mts_mapKey:key];
  166. if (!mappedKey)
  167. {
  168. [self mts_ignoredSetValue:value forUndefinedMappingKey:key];
  169. return;
  170. }
  171. if (value == [NSNull null])
  172. value = nil;
  173. if ([value isKindOfClass:NSArray.class])
  174. {
  175. __block NSMutableArray *modifiedArray = nil;
  176. [value enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id object, NSUInteger idx, BOOL *stop) {
  177. id validatedObject = object;
  178. NSError *error = nil;
  179. BOOL validated = [self mts_validateArrayObject:&validatedObject forArrayKey:mappedKey error:&error];
  180. // Automatic validation only if the value has not been manually validated
  181. if (object == validatedObject && validated)
  182. {
  183. Class typeClass = [self.class mts_cachedArrayClassMapping][mappedKey];
  184. if (typeClass)
  185. validated = [self mts_validateAutomaticallyValue:&validatedObject toClass:typeClass forKey:mappedKey];
  186. }
  187. if (validated)
  188. {
  189. if (validatedObject != object)
  190. {
  191. if (!modifiedArray)
  192. modifiedArray = [value mutableCopy];
  193. modifiedArray[idx] = validatedObject;
  194. }
  195. }
  196. else
  197. {
  198. if (!modifiedArray)
  199. modifiedArray = [value mutableCopy];
  200. [modifiedArray removeObjectAtIndex:idx];
  201. [self mts_invalidValue:validatedObject forArrayKey:mappedKey error:error];
  202. }
  203. }];
  204. if (modifiedArray)
  205. value = modifiedArray;
  206. }
  207. id originalValue = value;
  208. NSError *error = nil;
  209. BOOL validated = [self mts_validateValue:&value forKey:mappedKey error:&error];
  210. // Automatic validation only if the value has not been manually validated
  211. if (originalValue == value && validated)
  212. validated = [self mts_validateAutomaticallyValue:&value forKey:mappedKey];
  213. if (validated)
  214. {
  215. [self setValue:value forKey:mappedKey];
  216. }
  217. else
  218. [self mts_invalidValue:value forKey:mappedKey error:error];
  219. }
  220. - (void)mts_setValuesForKeysWithDictionary:(NSDictionary *)keyedValues
  221. {
  222. for (NSString *key in keyedValues)
  223. {
  224. // For each key-value pair in the dictionary
  225. NSArray *allKeyPath = [self.class mts_keyPaths][key];
  226. BOOL attemptedToSetOriginalKey = NO; // <-- YES when value for "key" has been set
  227. for (MTSKeyPath *keyPath in allKeyPath)
  228. {
  229. // For each key related keyPath
  230. BOOL validKeyPath = NO;
  231. id value = keyedValues;
  232. NSUInteger count = keyPath.components.count;
  233. for (NSInteger i=0; i<count; ++i)
  234. {
  235. // For each keyPath component
  236. NSString *path = keyPath.components[i];
  237. value = [value valueForKey:path];
  238. if (i < count - 1)
  239. {
  240. if (![value isKindOfClass:NSDictionary.class]) // <-- Checking that the KeyPath is only accessing dictionary objects.
  241. break;
  242. }
  243. else
  244. {
  245. validKeyPath = YES;
  246. }
  247. }
  248. if (value && validKeyPath)
  249. {
  250. if (!attemptedToSetOriginalKey && [keyPath.key isEqualToString:key])
  251. attemptedToSetOriginalKey = YES;
  252. [self mts_setValue:value forKey:keyPath.key];
  253. }
  254. }
  255. // If "key" has not been listed in Motis available keyPaths list,
  256. // lets perform the setter of the value for the given key.
  257. if (!attemptedToSetOriginalKey)
  258. [self mts_setValue:keyedValues[key] forKey:key];
  259. }
  260. }
  261. - (id)mts_valueForKey:(NSString*)key
  262. {
  263. NSString *mappedKey = [self mts_mapKey:key];
  264. if (!mappedKey)
  265. return nil;
  266. id value = [self valueForKey:mappedKey];
  267. return value;
  268. }
  269. - (NSDictionary*)mts_dictionaryWithValuesForKeys:(NSArray *)keys
  270. {
  271. NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:keys.count];
  272. for (NSString *key in keys)
  273. {
  274. id value = [self mts_valueForKey:key];
  275. if (!value)
  276. value = [NSNull null];
  277. dictionary[key] = value;
  278. }
  279. return [dictionary copy];
  280. }
  281. - (NSString*)mts_extendedObjectDescription
  282. {
  283. NSString *description = self.description;
  284. NSArray *keys = [[self.class mts_cachedMapping] allValues];
  285. if (keys.count > 0)
  286. {
  287. NSDictionary *keyValues = [self dictionaryWithValuesForKeys:keys];
  288. return [NSString stringWithFormat:@"%@ - Mapped Values: %@", description, [keyValues description]];
  289. }
  290. return description;
  291. }
  292. #pragma mark Private Methods
  293. - (NSString*)mts_mapKey:(NSString*)key
  294. {
  295. NSDictionary *mapping = [self.class mts_cachedMapping];
  296. NSString *mappedKey = mapping[key];
  297. if (mappedKey)
  298. return mappedKey;
  299. if ([self.class mts_shouldSetUndefinedKeys])
  300. return key;
  301. return nil;
  302. }
  303. @end
  304. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  305. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  306. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  307. #pragma mark - Motis_Subclassing
  308. @implementation NSObject (Motis_Subclassing)
  309. + (NSDictionary*)mts_mapping
  310. {
  311. // Subclasses must override, always adding super to the mapping!
  312. return @{};
  313. }
  314. + (BOOL)mts_shouldSetUndefinedKeys
  315. {
  316. // Subclasses might override.
  317. NSDictionary *mapping = [self mts_cachedMapping];
  318. return mapping.count == 0;
  319. }
  320. + (NSDictionary*)mts_arrayClassMapping
  321. {
  322. // Subclasses might override.
  323. return @{};
  324. }
  325. - (id)mts_willCreateObjectOfClass:(Class)typeClass withDictionary:(NSDictionary*)dictionary forKey:(NSString*)key abort:(BOOL*)abort;
  326. {
  327. // Subclasses might override.
  328. return nil;
  329. }
  330. - (void)mts_didCreateObject:(id)object forKey:(NSString *)key
  331. {
  332. // Subclasses might override.
  333. }
  334. + (NSDateFormatter*)mts_validationDateFormatter
  335. {
  336. // Subclasses may override and return a custom formatter.
  337. return nil;
  338. }
  339. - (BOOL)mts_validateValue:(inout __autoreleasing id *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing *)outError
  340. {
  341. // Subclasses might override.
  342. return [self validateValue:ioValue forKey:inKey error:outError];
  343. }
  344. - (BOOL)mts_validateArrayObject:(inout __autoreleasing id *)ioValue forArrayKey:(NSString *)arrayKey error:(out NSError *__autoreleasing *)outError
  345. {
  346. // Subclasses might override.
  347. return YES;
  348. }
  349. - (void)mts_ignoredSetValue:(id)value forUndefinedMappingKey:(NSString*)key
  350. {
  351. // Subclasses might override.
  352. MJLog(@"Undefined Mapping Key <%@> in class %@.", key, [self.class description]);
  353. }
  354. - (void)mts_invalidValue:(id)value forKey:(NSString *)key error:(NSError*)error
  355. {
  356. // Subclasses might override.
  357. MJLog(@"Value for Key <%@> is not valid in class %@. Error: %@", key, [self.class description], error);
  358. }
  359. - (void)mts_invalidValue:(id)value forArrayKey:(NSString *)key error:(NSError*)error
  360. {
  361. // Subclasses might override.
  362. MJLog(@"Item for ArrayKey <%@> is not valid in class %@. Error: %@", key, [self.class description], error);
  363. }
  364. @end
  365. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  366. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  367. // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ //
  368. #pragma mark - Motis_Private
  369. @implementation NSObject (Motis_Private)
  370. __attribute__((constructor))
  371. static void mts_motisInitialization()
  372. {
  373. [NSObject mts_cachedMapping];
  374. [NSObject mts_cachedArrayClassMapping];
  375. [NSObject mts_keyPaths];
  376. [NSObject mts_decimalFormatterAllowFloats];
  377. [NSObject mts_decimalFormatterNoFloats];
  378. }
  379. + (NSDictionary*)mts_cachedMapping
  380. {
  381. static NSMutableDictionary *mappings = nil;
  382. static dispatch_once_t onceToken1;
  383. dispatch_once(&onceToken1, ^{
  384. mappings = [NSMutableDictionary dictionary];
  385. });
  386. NSString *className = stringFromClass(self);
  387. NSDictionary *mapping = mappings[className];
  388. if (!mapping)
  389. {
  390. Class superClass = [self superclass];
  391. NSMutableDictionary *dictionary = nil;
  392. if ([superClass isSubclassOfClass:NSObject.class])
  393. dictionary = [[superClass mts_cachedMapping] mutableCopy];
  394. else
  395. dictionary = [NSMutableDictionary dictionary];
  396. [dictionary addEntriesFromDictionary:[self mts_mapping]];
  397. mapping = [dictionary copy];
  398. mappings[className] = mapping;
  399. }
  400. return mapping;
  401. }
  402. + (NSDictionary*)mts_cachedArrayClassMapping
  403. {
  404. static NSMutableDictionary *arrayClassMappings = nil;
  405. static dispatch_once_t onceToken1;
  406. dispatch_once(&onceToken1, ^{
  407. arrayClassMappings = [NSMutableDictionary dictionary];
  408. });
  409. NSString *className = stringFromClass(self);
  410. NSDictionary *arrayClassMapping = arrayClassMappings[className];
  411. if (!arrayClassMapping)
  412. {
  413. Class superClass = [self superclass];
  414. NSMutableDictionary *mapping = nil;
  415. if ([superClass isSubclassOfClass:NSObject.class])
  416. mapping = [[superClass mts_cachedArrayClassMapping] mutableCopy];
  417. else
  418. mapping = [NSMutableDictionary dictionary];
  419. [mapping addEntriesFromDictionary:[self mts_arrayClassMapping]];
  420. arrayClassMapping = [mapping copy];
  421. arrayClassMappings[className] = arrayClassMapping;
  422. }
  423. return arrayClassMapping;
  424. }
  425. + (NSDictionary*)mts_keyPaths
  426. {
  427. static NSMutableDictionary *classKeyPaths = nil;
  428. static dispatch_once_t onceToken1;
  429. dispatch_once(&onceToken1, ^{
  430. classKeyPaths = [NSMutableDictionary dictionary];
  431. });
  432. NSString *className = stringFromClass(self);
  433. NSDictionary *keyPaths = classKeyPaths[className];
  434. if (!keyPaths)
  435. {
  436. Class superClass = [self superclass];
  437. NSMutableDictionary *dict = nil;
  438. if ([superClass isSubclassOfClass:NSObject.class])
  439. dict = [[superClass mts_keyPaths] mutableCopy];
  440. else
  441. dict = [NSMutableDictionary dictionary];
  442. NSDictionary *mapping = [self mts_mapping];
  443. for (NSString *key in mapping)
  444. {
  445. MTSKeyPath *keyPath = [[MTSKeyPath alloc] initWithKeyPath:key];
  446. NSString *firstPath = keyPath.components[0];
  447. NSMutableArray *listOfKeyPaths = dict[firstPath];
  448. if (!listOfKeyPaths)
  449. {
  450. listOfKeyPaths = [NSMutableArray array];
  451. dict[firstPath] = listOfKeyPaths;
  452. }
  453. [listOfKeyPaths addObject:keyPath];
  454. }
  455. keyPaths = [dict copy];
  456. classKeyPaths[className] = keyPaths;
  457. }
  458. return keyPaths;
  459. }
  460. - (NSString*)mts_typeAttributeForKey:(NSString*)key
  461. {
  462. static NSMutableDictionary *typeAttributes = nil;
  463. static dispatch_once_t onceToken;
  464. dispatch_once(&onceToken, ^{
  465. typeAttributes = [NSMutableDictionary dictionary];
  466. });
  467. NSMutableDictionary *classTypeAttributes = typeAttributes[stringFromClass(self.class)];
  468. if (!classTypeAttributes)
  469. {
  470. classTypeAttributes = [NSMutableDictionary dictionary];
  471. typeAttributes[stringFromClass(self.class)] = classTypeAttributes;
  472. }
  473. NSString *typeAttribute = classTypeAttributes[key];
  474. if (typeAttribute)
  475. return typeAttribute;
  476. objc_property_t property = class_getProperty(self.class, key.UTF8String);
  477. if (!property)
  478. return nil;
  479. const char * type = property_getAttributes(property);
  480. NSString * typeString = @(type);
  481. NSArray * attributes = [typeString componentsSeparatedByString:@","];
  482. typeAttribute = attributes[0];
  483. classTypeAttributes[key] = typeAttribute;
  484. return typeAttribute;
  485. }
  486. - (BOOL)mts_isClassTypeTypeAttribute:(NSString*)typeAttribute
  487. {
  488. static NSMutableDictionary *dictionary = nil;
  489. static dispatch_once_t onceToken;
  490. dispatch_once(&onceToken, ^{
  491. dictionary = [NSMutableDictionary dictionary];
  492. });
  493. NSNumber *isClassType = dictionary[typeAttribute];
  494. if (!isClassType)
  495. {
  496. isClassType = @([typeAttribute hasPrefix:@"T@"] && ([typeAttribute length] > 1));
  497. dictionary[typeAttribute] = isClassType;
  498. }
  499. return isClassType.boolValue;
  500. }
  501. - (void)mts_getClassName:(out NSString *__autoreleasing*)className protocols:(out NSArray *__autoreleasing*)protocols fromTypeAttribute:(NSString*)typeAttribute
  502. {
  503. static NSMutableDictionary *dictionary = nil;
  504. static dispatch_once_t onceToken;
  505. dispatch_once(&onceToken, ^{
  506. dictionary = [NSMutableDictionary dictionary];
  507. });
  508. NSArray *array = dictionary[typeAttribute];
  509. if (array)
  510. {
  511. if (array.count > 0)
  512. {
  513. *className = array[0];
  514. *protocols = array[1];
  515. }
  516. return;
  517. }
  518. if ([self mts_isClassTypeTypeAttribute:typeAttribute])
  519. {
  520. if (typeAttribute.length < 3)
  521. {
  522. *className = @"";
  523. return;
  524. }
  525. NSString *classAttribute = [typeAttribute substringWithRange:NSMakeRange(3, typeAttribute.length-4)];
  526. NSString *protocolNames = nil;
  527. if (classAttribute)
  528. {
  529. NSScanner *scanner = [NSScanner scannerWithString:classAttribute];
  530. [scanner scanUpToString:@"<" intoString:className];
  531. [scanner scanUpToString:@">" intoString:&protocolNames];
  532. }
  533. if (*className == nil)
  534. *className = @"";
  535. if (protocolNames.length > 0)
  536. {
  537. protocolNames = [protocolNames substringFromIndex:1];
  538. protocolNames = [protocolNames stringByReplacingOccurrencesOfString:@" " withString:@""];
  539. *protocols = [protocolNames componentsSeparatedByString:@","];
  540. }
  541. else
  542. *protocols = @[];
  543. NSArray *array = @[*className, *protocols];
  544. dictionary[typeAttribute] = array;
  545. }
  546. else
  547. {
  548. dictionary[typeAttribute] = @[];
  549. }
  550. }
  551. - (BOOL)mts_validateAutomaticallyValue:(inout __autoreleasing id *)ioValue forKey:(NSString*)key
  552. {
  553. if (*ioValue == nil)
  554. return YES;
  555. NSString * typeAttribute = [self mts_typeAttributeForKey:key];
  556. if (!typeAttribute) // <-- If no attribute, abort automatic validation
  557. return YES;
  558. NSString * propertyType = [typeAttribute substringFromIndex:1];
  559. const char * rawPropertyType = [propertyType UTF8String];
  560. if ([self mts_isClassTypeTypeAttribute:typeAttribute])
  561. {
  562. NSString *className = nil;
  563. NSArray *protocols = nil;
  564. [self mts_getClassName:&className protocols:&protocols fromTypeAttribute:typeAttribute];
  565. if (className.length == 0)
  566. {
  567. // It's an "id".
  568. // Actually, we should make a compare like this:
  569. // if (strcmp(rawPropertyType, @encode(id)) == 0)
  570. // return YES;
  571. //
  572. // However, becuase of the "if" statements, we know that our typeAttribute begins with a "@" and
  573. // if "className.length" == 0 means that the "rawPropertyType" to be compared is exactly an @encode(id).
  574. //
  575. // Therefore, we return directly YES.
  576. return YES;
  577. }
  578. else
  579. {
  580. Class typeClass = classFromString(className);
  581. if (typeClass)
  582. {
  583. MJLog(@"%@ --> %@", key, stringFromClass(typeClass));
  584. return [self mts_validateAutomaticallyValue:ioValue toClass:typeClass forKey:key];
  585. }
  586. }
  587. return NO;
  588. }
  589. else // because it is not a class, the property must be a basic type
  590. {
  591. if ([*ioValue isKindOfClass:NSNumber.class])
  592. {
  593. #if defined(__LP64__) && __LP64__
  594. // Nothing to do
  595. #else
  596. if (strcmp(rawPropertyType, @encode(BOOL)) == 0)
  597. {
  598. *ioValue = @([*ioValue boolValue]);
  599. return *ioValue != nil;
  600. }
  601. #endif
  602. // Conversion from NSNumber to basic types is already handled by the system.
  603. return YES;
  604. }
  605. else if ([*ioValue isKindOfClass:NSString.class])
  606. {
  607. if (strcmp(rawPropertyType, @encode(BOOL)) == 0)
  608. {
  609. if ([*ioValue isKindOfClass:NSString.class])
  610. {
  611. if ([*ioValue compare:@"true" options:NSCaseInsensitiveSearch] == NSOrderedSame)
  612. *ioValue = @YES;
  613. else if ([*ioValue compare:@"false" options:NSCaseInsensitiveSearch] == NSOrderedSame)
  614. *ioValue = @NO;
  615. else
  616. *ioValue = @([*ioValue doubleValue] != 0.0);
  617. return *ioValue != nil;
  618. }
  619. }
  620. else if (strcmp(rawPropertyType, @encode(unsigned long long)) == 0)
  621. {
  622. if ([*ioValue isKindOfClass:NSString.class])
  623. {
  624. *ioValue = [[self.class mts_decimalFormatterNoFloats] numberFromString:*ioValue];
  625. return *ioValue != nil;
  626. }
  627. }
  628. return YES;
  629. }
  630. else // If not a number and not a string, types cannot match.
  631. {
  632. return NO;
  633. }
  634. }
  635. }
  636. - (BOOL)mts_validateAutomaticallyValue:(inout __autoreleasing id *)ioValue toClass:(Class)typeClass forKey:(NSString*)key
  637. {
  638. // If types match, just return
  639. if ([*ioValue isKindOfClass:typeClass])
  640. return YES;
  641. // Otherwise, lets try to fit the desired class type
  642. // Because *ioValue comes frome a JSON deserialization, it can only be a string, number, array or dictionary.
  643. if ([*ioValue isKindOfClass:NSString.class]) // <-- STRINGS
  644. {
  645. if ([typeClass isSubclassOfClass:NSURL.class])
  646. {
  647. *ioValue = [NSURL URLWithString:*ioValue];
  648. return *ioValue != nil;
  649. }
  650. else if ([typeClass isSubclassOfClass:NSData.class])
  651. {
  652. *ioValue = [[NSData alloc] initWithBase64EncodedString:*ioValue options:NSDataBase64DecodingIgnoreUnknownCharacters];
  653. return *ioValue != nil;
  654. }
  655. else if ([typeClass isSubclassOfClass:NSNumber.class])
  656. {
  657. if ([*ioValue compare:@"true" options:NSCaseInsensitiveSearch] == NSOrderedSame)
  658. *ioValue = @YES;
  659. else if ([*ioValue compare:@"false" options:NSCaseInsensitiveSearch] == NSOrderedSame)
  660. *ioValue = @NO;
  661. else
  662. *ioValue = @([*ioValue doubleValue]);
  663. return *ioValue != nil;
  664. // Using NSNumberFormatter is slower!
  665. // NSNumberFormatter *formatter = [NSObject mts_decimalFormatterAllowFloats];
  666. // *ioValue = [formatter numberFromString:*ioValue];
  667. }
  668. if ([typeClass isSubclassOfClass:NSDate.class])
  669. {
  670. NSDateFormatter *dateFormatter = [self.class mts_validationDateFormatter];
  671. if (dateFormatter)
  672. {
  673. *ioValue = [dateFormatter dateFromString:*ioValue];
  674. return *ioValue != nil;
  675. }
  676. else
  677. {
  678. static NSCharacterSet *characterSet = nil;
  679. static dispatch_once_t onceToken;
  680. dispatch_once(&onceToken, ^{
  681. characterSet = [NSCharacterSet characterSetWithCharactersInString:@"1234567890.,"];
  682. });
  683. NSString *trimmedString = [*ioValue stringByTrimmingCharactersInSet:characterSet];
  684. if (trimmedString.length == 0)
  685. {
  686. NSTimeInterval timeInterval = [*ioValue doubleValue];
  687. *ioValue = [NSDate dateWithTimeIntervalSince1970:timeInterval];
  688. return *ioValue != nil;
  689. }
  690. // Another approach using REGEX to validate the number (slower):
  691. // static NSRegularExpression *regex = nil;
  692. // static dispatch_once_t onceToken;
  693. // dispatch_once(&onceToken, ^{
  694. // regex = [[NSRegularExpression alloc] initWithPattern:@"\\d*[.]?\\d+"
  695. // options:NSRegularExpressionCaseInsensitive
  696. // error:nil];
  697. // });
  698. //
  699. // NSTextCheckingResult *match = [regex firstMatchInString:*ioValue options:0 range:NSMakeRange(0, [*ioValue length])];
  700. // if (match && match.range.location == 0 && match.range.length == [*ioValue length])
  701. // {
  702. // NSTimeInterval timeInterval = [*ioValue doubleValue];
  703. // *ioValue = [NSDate dateWithTimeIntervalSince1970:timeInterval];
  704. // return *ioValue != nil;
  705. // }
  706. }
  707. }
  708. }
  709. else if ([*ioValue isKindOfClass:NSNumber.class]) // <-- NUMBERS
  710. {
  711. if ([typeClass isSubclassOfClass:NSDate.class])
  712. {
  713. *ioValue = [NSDate dateWithTimeIntervalSince1970:[*ioValue doubleValue]];
  714. return *ioValue != nil;
  715. }
  716. else if ([typeClass isSubclassOfClass:NSString.class])
  717. {
  718. *ioValue = [*ioValue stringValue];
  719. return *ioValue != nil;
  720. }
  721. }
  722. else if ([*ioValue isKindOfClass:NSArray.class]) // <-- ARRAYS
  723. {
  724. if ([typeClass isSubclassOfClass:NSMutableArray.class])
  725. {
  726. *ioValue = [NSMutableArray arrayWithArray:*ioValue];
  727. return *ioValue != nil;
  728. }
  729. else if ([typeClass isSubclassOfClass:NSMutableSet.class])
  730. {
  731. *ioValue = [NSMutableSet setWithArray:*ioValue];
  732. return *ioValue != nil;
  733. }
  734. else if ([typeClass isSubclassOfClass:NSSet.class])
  735. {
  736. *ioValue = [NSSet setWithArray:*ioValue];
  737. return *ioValue != nil;
  738. }
  739. else if ([typeClass isSubclassOfClass:NSMutableOrderedSet.class])
  740. {
  741. *ioValue = [NSMutableOrderedSet orderedSetWithArray:*ioValue];
  742. return *ioValue != nil;
  743. }
  744. else if ([typeClass isSubclassOfClass:NSOrderedSet.class])
  745. {
  746. *ioValue = [NSOrderedSet orderedSetWithArray:*ioValue];
  747. return *ioValue != nil;
  748. }
  749. }
  750. else if ([*ioValue isKindOfClass:NSDictionary.class]) // <-- DICTIONARIES
  751. {
  752. if ([typeClass isSubclassOfClass:NSMutableDictionary.class])
  753. {
  754. *ioValue = [NSMutableDictionary dictionaryWithDictionary:*ioValue];
  755. return *ioValue != nil;
  756. }
  757. else if ([typeClass isSubclassOfClass:NSObject.class])
  758. {
  759. BOOL abort = NO;
  760. id instance = [self mts_willCreateObjectOfClass:typeClass withDictionary:*ioValue forKey:key abort:&abort];
  761. if (abort)
  762. return NO;
  763. if (!instance)
  764. {
  765. instance = [[typeClass alloc] init];
  766. [instance mts_setValuesForKeysWithDictionary:*ioValue];
  767. }
  768. [self mts_didCreateObject:instance forKey:key];
  769. *ioValue = instance;
  770. return *ioValue != nil;
  771. }
  772. }
  773. return NO;
  774. }
  775. #pragma mark Private Helpers
  776. + (NSNumberFormatter*)mts_decimalFormatterAllowFloats
  777. {
  778. static NSNumberFormatter *decimalFormatter = nil;
  779. static dispatch_once_t onceToken;
  780. dispatch_once(&onceToken, ^{
  781. decimalFormatter = [NSNumberFormatter new];
  782. decimalFormatter.numberStyle = NSNumberFormatterDecimalStyle;
  783. decimalFormatter.allowsFloats = YES;
  784. });
  785. return decimalFormatter;
  786. }
  787. + (NSNumberFormatter*)mts_decimalFormatterNoFloats
  788. {
  789. static NSNumberFormatter *decimalFormatter = nil;
  790. static dispatch_once_t onceToken;
  791. dispatch_once(&onceToken, ^{
  792. decimalFormatter = [NSNumberFormatter new];
  793. decimalFormatter.numberStyle = NSNumberFormatterDecimalStyle;
  794. decimalFormatter.allowsFloats = NO;
  795. });
  796. return decimalFormatter;
  797. }
  798. @end