Skip to content

Commit eb8c67b

Browse files
committed
Added some comments to the base model code.
1 parent d742903 commit eb8c67b

File tree

1 file changed

+46
-1
lines changed

1 file changed

+46
-1
lines changed

KVC Validation Pattern/KVC Validation Pattern/Model/CTCBaseModel.m

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@ @interface CTCBaseModel ()
3636
@implementation CTCBaseModel {
3737
dispatch_once_t keyToken;
3838
}
39-
static NSMutableDictionary *modelProperties;
39+
static NSMutableDictionary *modelProperties; // Dictionary used to convert json key's into classes proper-cased keys
4040
static dispatch_once_t onceToken;
4141
static NSArray *propertyTypesArray;
4242

4343
+ (void)initialize {
4444
[super initialize];
4545

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
4648
dispatch_once(&onceToken, ^{
4749
modelProperties = [NSMutableDictionary dictionary];
4850

@@ -63,19 +65,30 @@ + (void)initialize {
6365
});
6466

6567
NSMutableDictionary *translateNameDict = [NSMutableDictionary dictionary];
68+
// This method will build our class key mapping as well as adding our validation methods to the class dynamically
6669
[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.
6772
[modelProperties setObject:translateNameDict forKey:[self calculateClassName]];
6873
}
6974

7075
- (id)initWithDictionary:(NSDictionary *)dictionary {
7176
self = [self init];
7277
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];
7383
[self setValuesForKeysWithDictionary:dictionary];
7484
}
7585
return self;
7686
}
7787

7888
- (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.
7992
dispatch_once(&keyToken, ^{
8093
_dictionaryKey = [[self class] calculateClassName];
8194
});
@@ -85,35 +98,56 @@ - (NSString *)dictionaryKey {
8598
#pragma mark - KVC methods
8699

87100
- (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)
88103
[self setValue:value forKey:key properCase:NO];
89104
}
90105

91106
- (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.
92112
NSString *newKey = self.undefinedKeys[[key lowercaseString]];
93113
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.
94117
[self setValue:value forKey:newKey properCase:YES];
95118
}
96119
}
97120

98121
- (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.
99126
if (!properCase) {
100127
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.
101130
NSString *properKey = modelProperties[self.dictionaryKey][lowerCaseKey];
102131
if (properKey){
103132
key = properKey;
104133
}
105134
}
106135

107136
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.
108139
BOOL isValid = [self validateValue:&value forKey:key error:&error];
109140

141+
// We only want to set the value if the value is valid.
110142
if (isValid) {
111143
[super setValue:value forKey:key];
112144
}
113145
}
114146

115147

116148
+ (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.
117151
NSString *className = NSStringFromClass([self class]);
118152
NSRange ido = [className rangeOfString:@"-"];
119153
NSUInteger cdo = ido.location;
@@ -124,23 +158,31 @@ + (NSString *)calculateClassName {
124158
}
125159

126160
+ (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.
127163
if (!class || class == [NSObject class]){
128164
return;
129165
}
130166

131167
unsigned int outCount, i;
168+
// Get the class property list.
132169
objc_property_t *properties = class_copyPropertyList(class, &outCount);
133170
for (i = 0; i < outCount; i++){
134171
objc_property_t p = properties[i];
172+
// Get the property name.
135173
const char *name = property_getName(p);
136174
NSString *nsName = [[NSString alloc] initWithCString:name encoding:NSUTF8StringEncoding];
175+
// The lower case property name will be the key in the dictionary
137176
NSString *lowerCaseName = [nsName lowercaseString];
138177
[translateDictionary setObject:nsName forKey:lowerCaseName];
178+
// Determine the string representation of the property type
139179
NSString *propertyType = [self getPropertyType:p];
180+
// Add a validation method for this property to the class
140181
[self addValidatorForProperty:nsName type:propertyType];
141182
}
142183
free(properties);
143184

185+
// Call this method again with this classes super class to get properties defined on the super.
144186
[self hydrateModelProperties:class_getSuperclass(class) translateDictionary:translateDictionary];
145187
}
146188

@@ -173,6 +215,7 @@ + (void)addValidatorForProperty:(NSString *)propertyName type:(NSString *)proper
173215
IMP implementation;
174216
CTCPropertyType type;
175217

218+
// Look up the string property type in the propertyTypes array
176219
NSUInteger n = [propertyTypesArray indexOfObject:propertyType];
177220

178221
if (n == NSNotFound){
@@ -219,7 +262,9 @@ + (void)addValidatorForProperty:(NSString *)propertyName type:(NSString *)proper
219262
break;
220263
}
221264
if (implementation){
265+
// This method creates the validation method name in the format of validate<KeyName>:error: format
222266
NSString *methodName = [self generateValidationMethodName:propertyName];
267+
// This will actually add the validation method to the class
223268
class_addMethod([self class], NSSelectorFromString(methodName), implementation, "c@:^@^@");
224269
}
225270
}

0 commit comments

Comments
 (0)