@@ -27,19 +27,25 @@ typedef NS_ENUM(NSUInteger, CTCPropertyType){
27
27
CTCPropertyTypeDouble // Property is a double
28
28
};
29
29
30
+
30
31
@interface CTCBaseModel ()
31
32
32
33
@property (nonatomic , readwrite , strong ) NSString *dictionaryKey;
33
34
34
35
@end
35
36
37
+
36
38
@implementation CTCBaseModel {
37
39
dispatch_once_t keyToken;
38
40
}
41
+
39
42
static NSMutableDictionary *modelProperties; // Dictionary used to convert json key's into classes proper-cased keys
40
43
static dispatch_once_t onceToken;
41
44
static NSArray *propertyTypesArray;
42
45
46
+
47
+ #pragma mark - Class methods
48
+
43
49
+ (void )initialize {
44
50
[super initialize ];
45
51
@@ -72,79 +78,6 @@ + (void)initialize {
72
78
[modelProperties setObject: translateNameDict forKey: [self calculateClassName ]];
73
79
}
74
80
75
- - (id )initWithDictionary : (NSDictionary *)dictionary {
76
- self = [self init ];
77
- if (self){
78
- // This is where all the KVC magic begins. This method can be called on an object that has already been
79
- // created. This is just a convenience initializer. The same thing could be accomplished in the following manner:
80
- //
81
- // MySubClass *subClass = [MySubClass new];
82
- // [subClass setValuesForKeysWithDictionary:dictionary];
83
- [self setValuesForKeysWithDictionary: dictionary];
84
- }
85
- return self;
86
- }
87
-
88
- - (NSString *)dictionaryKey {
89
- // This will cause this key to be generated once, on a per-class/per-instance basis. This is done
90
- // because the logic in calculateClassName can be rather expensive when called repeatedly on
91
- // every property, for every class.
92
- dispatch_once (&keyToken, ^{
93
- _dictionaryKey = [[self class ] calculateClassName ];
94
- });
95
- return _dictionaryKey;
96
- }
97
-
98
- #pragma mark - KVC methods
99
-
100
- - (void )setValue : (id )value forKey : (NSString *)key {
101
- // We can assume at this point, the key is not in proper case. We will call the set value method
102
- // with proper casing NO and it will figure out the proper casing for the key (if it can)
103
- [self setValue: value forKey: key properCase: NO ];
104
- }
105
-
106
- - (void )setValue : (id )value forUndefinedKey : (NSString *)key {
107
- // We will look in the undefinedKeys Dictionary (should be defined in the subclass), and look for
108
- // this undefined key to see if the class maps the key to a know key. This method is called automatically
109
- // by KVC if setValue:forKey: cannot find a property to match the key. Also, we wanted to implement
110
- // this method in the base class, because the default implementation of this on NSObject throws
111
- // an exception.
112
- NSString *newKey = self.undefinedKeys [[key lowercaseString ]];
113
- if (newKey){
114
- // If we have found the key, we will call our set value for key with proper case set to YES because
115
- // the key should be put into the dictionary in proper case. If it is not, this will not work, and our value
116
- // will never be set.
117
- [self setValue: value forKey: newKey properCase: YES ];
118
- }
119
- }
120
-
121
- - (void )setValue : (id )value forKey : (NSString *)key properCase : (BOOL )properCase {
122
- // We first check to see if the key is already in proper case. If not, we'll make it proper case. This is done
123
- // because calling lowerCaseString on a string value can get expensive when called repeatedly. That is what
124
- // will happen when setting a classes values from a dictionary as it will iteratively call setValue:forKey: on every
125
- // key it finds in the dictionary.
126
- if (!properCase) {
127
- NSString *lowerCaseKey = [key lowercaseString ];
128
- // We do the lookup in the modelProperties dictionary for the proper cased key. If we find it, we change
129
- // the key to be that value.
130
- NSString *properKey = modelProperties[self .dictionaryKey][lowerCaseKey];
131
- if (properKey){
132
- key = properKey;
133
- }
134
- }
135
-
136
- NSError *error = nil ;
137
- // Here we call the validation logic. Calling validateValue:forKey:error: will cause validate<key>:error: to be called
138
- // so all the validation methods we added dynamically to this class in initialize will no be utilised.
139
- BOOL isValid = [self validateValue: &value forKey: key error: &error];
140
-
141
- // We only want to set the value if the value is valid.
142
- if (isValid) {
143
- [super setValue: value forKey: key];
144
- }
145
- }
146
-
147
-
148
81
+ (NSString *)calculateClassName {
149
82
// This method is here because sometimes NSStringFromClass will return the class name with a - and some characters
150
83
// after the class name.
@@ -163,7 +96,7 @@ + (void)hydrateModelProperties:(Class)class translateDictionary:(NSMutableDictio
163
96
if (!class || class == [NSObject class ]){
164
97
return ;
165
98
}
166
-
99
+
167
100
unsigned int outCount, i;
168
101
// Get the class property list.
169
102
objc_property_t *properties = class_copyPropertyList (class, &outCount);
@@ -207,7 +140,7 @@ + (NSString *)getPropertyType:(objc_property_t)property {
207
140
propertyType = (const char *)[[NSData dataWithBytes: (attribute + 3 ) length: strlen (attribute) - 4 ] bytes ];
208
141
}
209
142
}
210
-
143
+
211
144
return [[NSString alloc ] initWithCString: propertyType encoding: NSUTF8StringEncoding];
212
145
}
213
146
@@ -217,13 +150,13 @@ + (void)addValidatorForProperty:(NSString *)propertyName type:(NSString *)proper
217
150
218
151
// Look up the string property type in the propertyTypes array
219
152
NSUInteger n = [propertyTypesArray indexOfObject: propertyType];
220
-
153
+
221
154
if (n == NSNotFound ){
222
155
type = CTCPropertyUnknown;
223
156
} else {
224
157
type = (CTCPropertyType)n;
225
158
}
226
-
159
+
227
160
switch (type){
228
161
case CTCPropertyTypeString:
229
162
implementation = (IMP )validateStringProperty;
@@ -273,4 +206,80 @@ +(NSString *)generateValidationMethodName:(NSString *)key{
273
206
return [NSString stringWithFormat: @" validate%@ :error:" , [NSString capitalizeFirstCharacter: key]];
274
207
}
275
208
209
+
210
+ #pragma mark - Life cycle
211
+
212
+ - (id )initWithDictionary : (NSDictionary *)dictionary {
213
+ self = [self init ];
214
+ if (self){
215
+ // This is where all the KVC magic begins. This method can be called on an object that has already been
216
+ // created. This is just a convenience initializer. The same thing could be accomplished in the following manner:
217
+ //
218
+ // MySubClass *subClass = [MySubClass new];
219
+ // [subClass setValuesForKeysWithDictionary:dictionary];
220
+ [self setValuesForKeysWithDictionary: dictionary];
221
+ }
222
+ return self;
223
+ }
224
+
225
+ - (NSString *)dictionaryKey {
226
+ // This will cause this key to be generated once, on a per-class/per-instance basis. This is done
227
+ // because the logic in calculateClassName can be rather expensive when called repeatedly on
228
+ // every property, for every class.
229
+ dispatch_once (&keyToken, ^{
230
+ _dictionaryKey = [[self class ] calculateClassName ];
231
+ });
232
+ return _dictionaryKey;
233
+ }
234
+
235
+
236
+ #pragma mark - KVC methods
237
+
238
+ - (void )setValue : (id )value forKey : (NSString *)key {
239
+ // We can assume at this point, the key is not in proper case. We will call the set value method
240
+ // with proper casing NO and it will figure out the proper casing for the key (if it can)
241
+ [self setValue: value forKey: key properCase: NO ];
242
+ }
243
+
244
+ - (void )setValue : (id )value forUndefinedKey : (NSString *)key {
245
+ // We will look in the undefinedKeys Dictionary (should be defined in the subclass), and look for
246
+ // this undefined key to see if the class maps the key to a know key. This method is called automatically
247
+ // by KVC if setValue:forKey: cannot find a property to match the key. Also, we wanted to implement
248
+ // this method in the base class, because the default implementation of this on NSObject throws
249
+ // an exception.
250
+ NSString *newKey = self.undefinedKeys [[key lowercaseString ]];
251
+ if (newKey){
252
+ // If we have found the key, we will call our set value for key with proper case set to YES because
253
+ // the key should be put into the dictionary in proper case. If it is not, this will not work, and our value
254
+ // will never be set.
255
+ [self setValue: value forKey: newKey properCase: YES ];
256
+ }
257
+ }
258
+
259
+ - (void )setValue : (id )value forKey : (NSString *)key properCase : (BOOL )properCase {
260
+ // We first check to see if the key is already in proper case. If not, we'll make it proper case. This is done
261
+ // because calling lowerCaseString on a string value can get expensive when called repeatedly. That is what
262
+ // will happen when setting a classes values from a dictionary as it will iteratively call setValue:forKey: on every
263
+ // key it finds in the dictionary.
264
+ if (!properCase) {
265
+ NSString *lowerCaseKey = [key lowercaseString ];
266
+ // We do the lookup in the modelProperties dictionary for the proper cased key. If we find it, we change
267
+ // the key to be that value.
268
+ NSString *properKey = modelProperties[self .dictionaryKey][lowerCaseKey];
269
+ if (properKey){
270
+ key = properKey;
271
+ }
272
+ }
273
+
274
+ NSError *error = nil ;
275
+ // Here we call the validation logic. Calling validateValue:forKey:error: will cause validate<key>:error: to be called
276
+ // so all the validation methods we added dynamically to this class in initialize will no be utilised.
277
+ BOOL isValid = [self validateValue: &value forKey: key error: &error];
278
+
279
+ // We only want to set the value if the value is valid.
280
+ if (isValid) {
281
+ [super setValue: value forKey: key];
282
+ }
283
+ }
284
+
276
285
@end
0 commit comments