RQBaseModel.m 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. //
  2. // RQBaseModel.m
  3. // RQCommon
  4. //
  5. // Created by 张嵘 on 2018/11/14.
  6. // Copyright © 2018 张嵘. All rights reserved.
  7. //
  8. #import "RQBaseModel.h"
  9. #import "RQReflection.h"
  10. #import "RQEXTRuntimeExtensions.h"
  11. // Used to cache the reflection performed in +propertyKeys.
  12. static void *RQObjectCachedPropertyKeysKey = &RQObjectCachedPropertyKeysKey;
  13. // Validates a value for an object and sets it if necessary.
  14. //
  15. // obj - The object for which the value is being validated. This value
  16. // must not be nil.
  17. // key - The name of one of `obj`s properties. This value must not be
  18. // nil.
  19. // value - The new value for the property identified by `key`.
  20. // forceUpdate - If set to `YES`, the value is being updated even if validating
  21. // it did not change it.
  22. // error - If not NULL, this may be set to any error that occurs during
  23. // validation
  24. //
  25. // Returns YES if `value` could be validated and set, or NO if an error
  26. // occurred.
  27. static BOOL SBValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUpdate, NSError **error) {
  28. // Mark this as being autoreleased, because validateValue may return
  29. // a new object to be stored in this variable (and we don't want ARC to
  30. // double-free or leak the old or new values).
  31. __autoreleasing id validatedValue = value;
  32. @try {
  33. if (![obj validateValue:&validatedValue forKey:key error:error]) return NO;
  34. if (forceUpdate || value != validatedValue) {
  35. [obj setValue:validatedValue forKey:key];
  36. }
  37. return YES;
  38. } @catch (NSException *ex) {
  39. NSLog(@"*** Caught exception setting key \"%@\" : %@", key, ex);
  40. // Fail fast in Debug builds.
  41. #if DEBUG
  42. @throw ex;
  43. #else
  44. if (error != NULL) {
  45. *error = [NSError rq_modelErrorWithException:ex];
  46. }
  47. return NO;
  48. #endif
  49. }
  50. }
  51. @implementation RQBaseModel
  52. /// 将 JSON (NSData,NSString,NSDictionary) 转换为 Model
  53. + (instancetype)modelWithJSON:(id)json { return [self yy_modelWithJSON:json]; }
  54. /// json-array 转 模型-数组
  55. + (NSArray *)modelArrayWithJSON:(id)json {
  56. return [NSArray yy_modelArrayWithClass:[self class] json:json];
  57. }
  58. /// 字典转模型
  59. + (instancetype)modelWithDictionary:(NSDictionary *)dictionary{
  60. return [self yy_modelWithDictionary:dictionary];
  61. }
  62. - (id)toJSONObject { return [self yy_modelToJSONObject]; }
  63. - (NSData *)toJSONData { return [self yy_modelToJSONData]; }
  64. - (NSString *)toJSONString { return [self yy_modelToJSONString]; }
  65. /// Coding/Copying/hash/equal
  66. - (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; }
  67. - (id)initWithCoder:(NSCoder *)aDecoder { return [self yy_modelInitWithCoder:aDecoder]; }
  68. - (id)copyWithZone:(NSZone *)zone { return [self yy_modelCopy]; }
  69. - (NSUInteger)hash { return [self yy_modelHash]; }
  70. - (BOOL)isEqual:(id)object { return [self yy_modelIsEqual:object]; }
  71. /// Properties optional
  72. - (void)setValue:(id)value forUndefinedKey:(NSString *)key { }
  73. /// desc
  74. - (NSString *)description { return [self yy_modelDescription]; }
  75. + (NSSet *)propertyKeys {
  76. NSSet *cachedKeys = objc_getAssociatedObject(self, RQObjectCachedPropertyKeysKey);
  77. if (cachedKeys != nil) return cachedKeys;
  78. NSMutableSet *keys = [NSMutableSet set];
  79. [self enumeratePropertiesUsingBlock:^(objc_property_t property, BOOL *stop) {
  80. rq_propertyAttributes *attributes = rq_copyPropertyAttributes(property);
  81. @onExit {
  82. free(attributes);
  83. };
  84. if (attributes->readonly && attributes->ivar == NULL) return;
  85. NSString *key = @(property_getName(property));
  86. [keys addObject:key];
  87. }];
  88. // It doesn't really matter if we replace another thread's work, since we do
  89. // it atomically and the result should be the same.
  90. objc_setAssociatedObject(self, RQObjectCachedPropertyKeysKey, keys, OBJC_ASSOCIATION_COPY);
  91. return keys;
  92. }
  93. - (NSDictionary *)dictionaryValue {
  94. return [self dictionaryWithValuesForKeys:self.class.propertyKeys.allObjects];
  95. }
  96. #pragma mark Reflection
  97. + (void)enumeratePropertiesUsingBlock:(void (^)(objc_property_t property, BOOL *stop))block {
  98. Class cls = self;
  99. BOOL stop = NO;
  100. while (!stop && ![cls isEqual:RQBaseModel.class]) {
  101. unsigned count = 0;
  102. objc_property_t *properties = class_copyPropertyList(cls, &count);
  103. cls = cls.superclass;
  104. if (properties == NULL) continue;
  105. @onExit {
  106. free(properties);
  107. };
  108. for (unsigned i = 0; i < count; i++) {
  109. block(properties[i], &stop);
  110. if (stop) break;
  111. }
  112. }
  113. }
  114. #pragma mark Merging
  115. - (void)mergeValueForKey:(NSString *)key fromModel:(RQBaseModel *)model {
  116. NSParameterAssert(key != nil);
  117. SEL selector = RQSelectorWithCapitalizedKeyPattern("merge", key, "FromModel:");
  118. if (![self respondsToSelector:selector]) {
  119. if (model != nil) {
  120. [self setValue:[model valueForKey:key] forKey:key];
  121. }
  122. return;
  123. }
  124. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]];
  125. invocation.target = self;
  126. invocation.selector = selector;
  127. [invocation setArgument:&model atIndex:2];
  128. [invocation invoke];
  129. }
  130. - (void)mergeValuesForKeysFromModel:(RQBaseModel *)model {
  131. NSSet *propertyKeys = model.class.propertyKeys;
  132. for (NSString *key in self.class.propertyKeys) {
  133. if (![propertyKeys containsObject:key]) continue;
  134. [self mergeValueForKey:key fromModel:model];
  135. }
  136. }
  137. #pragma mark Validation
  138. - (BOOL)validate:(NSError **)error {
  139. for (NSString *key in self.class.propertyKeys) {
  140. id value = [self valueForKey:key];
  141. BOOL success = SBValidateAndSetValue(self, key, value, NO, error);
  142. if (!success) return NO;
  143. }
  144. return YES;
  145. }
  146. @end