@@ -36,13 +36,15 @@ @interface CTCBaseModel ()
36
36
@implementation CTCBaseModel {
37
37
dispatch_once_t keyToken;
38
38
}
39
- static NSMutableDictionary *modelProperties;
39
+ static NSMutableDictionary *modelProperties; // Dictionary used to convert json key's into classes proper-cased keys
40
40
static dispatch_once_t onceToken;
41
41
static NSArray *propertyTypesArray;
42
42
43
43
+ (void )initialize {
44
44
[super initialize ];
45
45
46
+ // Initialize will be called for each subclass. Since all subclasses will share the modelProperties
47
+ // we do this in a dispatch_once block to ensure thread-safety
46
48
dispatch_once (&onceToken, ^{
47
49
modelProperties = [NSMutableDictionary dictionary ];
48
50
@@ -63,19 +65,30 @@ + (void)initialize {
63
65
});
64
66
65
67
NSMutableDictionary *translateNameDict = [NSMutableDictionary dictionary ];
68
+ // This method will build our class key mapping as well as adding our validation methods to the class dynamically
66
69
[self hydrateModelProperties: [self class ] translateDictionary: translateNameDict];
70
+ // Each subclass has an entry in the modelProperties dictionary with all of it's property names, we use the calculated
71
+ // class name as the key.
67
72
[modelProperties setObject: translateNameDict forKey: [self calculateClassName ]];
68
73
}
69
74
70
75
- (id )initWithDictionary : (NSDictionary *)dictionary {
71
76
self = [self init ];
72
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];
73
83
[self setValuesForKeysWithDictionary: dictionary];
74
84
}
75
85
return self;
76
86
}
77
87
78
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.
79
92
dispatch_once (&keyToken, ^{
80
93
_dictionaryKey = [[self class ] calculateClassName ];
81
94
});
@@ -85,35 +98,56 @@ - (NSString *)dictionaryKey {
85
98
#pragma mark - KVC methods
86
99
87
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)
88
103
[self setValue: value forKey: key properCase: NO ];
89
104
}
90
105
91
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.
92
112
NSString *newKey = self.undefinedKeys [[key lowercaseString ]];
93
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.
94
117
[self setValue: value forKey: newKey properCase: YES ];
95
118
}
96
119
}
97
120
98
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.
99
126
if (!properCase) {
100
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.
101
130
NSString *properKey = modelProperties[self .dictionaryKey][lowerCaseKey];
102
131
if (properKey){
103
132
key = properKey;
104
133
}
105
134
}
106
135
107
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.
108
139
BOOL isValid = [self validateValue: &value forKey: key error: &error];
109
140
141
+ // We only want to set the value if the value is valid.
110
142
if (isValid) {
111
143
[super setValue: value forKey: key];
112
144
}
113
145
}
114
146
115
147
116
148
+ (NSString *)calculateClassName {
149
+ // This method is here because sometimes NSStringFromClass will return the class name with a - and some characters
150
+ // after the class name.
117
151
NSString *className = NSStringFromClass ([self class ]);
118
152
NSRange ido = [className rangeOfString: @" -" ];
119
153
NSUInteger cdo = ido.location ;
@@ -124,23 +158,31 @@ + (NSString *)calculateClassName {
124
158
}
125
159
126
160
+ (void )hydrateModelProperties : (Class )class translateDictionary : (NSMutableDictionary *)translateDictionary {
161
+ // This method is called recursively to find all the properties in each super class, up to NSObject, then
162
+ // we stop.
127
163
if (!class || class == [NSObject class ]){
128
164
return ;
129
165
}
130
166
131
167
unsigned int outCount, i;
168
+ // Get the class property list.
132
169
objc_property_t *properties = class_copyPropertyList (class, &outCount);
133
170
for (i = 0 ; i < outCount; i++){
134
171
objc_property_t p = properties[i];
172
+ // Get the property name.
135
173
const char *name = property_getName (p);
136
174
NSString *nsName = [[NSString alloc ] initWithCString: name encoding: NSUTF8StringEncoding];
175
+ // The lower case property name will be the key in the dictionary
137
176
NSString *lowerCaseName = [nsName lowercaseString ];
138
177
[translateDictionary setObject: nsName forKey: lowerCaseName];
178
+ // Determine the string representation of the property type
139
179
NSString *propertyType = [self getPropertyType: p];
180
+ // Add a validation method for this property to the class
140
181
[self addValidatorForProperty: nsName type: propertyType];
141
182
}
142
183
free (properties);
143
184
185
+ // Call this method again with this classes super class to get properties defined on the super.
144
186
[self hydrateModelProperties: class_getSuperclass (class) translateDictionary: translateDictionary];
145
187
}
146
188
@@ -173,6 +215,7 @@ + (void)addValidatorForProperty:(NSString *)propertyName type:(NSString *)proper
173
215
IMP implementation;
174
216
CTCPropertyType type;
175
217
218
+ // Look up the string property type in the propertyTypes array
176
219
NSUInteger n = [propertyTypesArray indexOfObject: propertyType];
177
220
178
221
if (n == NSNotFound ){
@@ -219,7 +262,9 @@ + (void)addValidatorForProperty:(NSString *)propertyName type:(NSString *)proper
219
262
break ;
220
263
}
221
264
if (implementation){
265
+ // This method creates the validation method name in the format of validate<KeyName>:error: format
222
266
NSString *methodName = [self generateValidationMethodName: propertyName];
267
+ // This will actually add the validation method to the class
223
268
class_addMethod ([self class ], NSSelectorFromString (methodName), implementation, " c@:^@^@" );
224
269
}
225
270
}
0 commit comments