Understanding Key‑Value Coding (KVC) in Objective‑C: Principles, Execution Flow, and Advanced Applications
This article analyses the inner workings of Objective‑C’s Key‑Value Coding (KVC) by dissecting its setter and getter generation, primitive value handling, NSNumber/NSValue wrapping, and demonstrates advanced usage such as batch property updates, aggregation, and data filtering in real‑world projects.
Through a detailed case study of the Foundation framework’s KVC implementation, the article explains why assigning a string to an integer property via KVC does not crash, and outlines the underlying mechanisms that make this possible.
What is KVC? KVC (Key‑Value Coding) allows indirect access to an object’s properties using string keys. Core methods include -valueForKey: , -setValue:forKey: , -setNilValueForKey: , -setValue:forUndefinedKey: , and -valueForUndefinedKey: .
Setter Generation
+ (DSKeyValueSetter *)_d_createValueSetterWithContainerClassID:(id)containerClassID key:(NSString *)key {
DSKeyValueSetter *setter = nil;
// ...
Method method = NULL;
// Search for set
, _set
, setIs
if ((method = DSKeyValueMethodForPattern(self, "set%s:", key_cstr_upfirst)) ||
(method = DSKeyValueMethodForPattern(self, "_set%s:", key_cstr_upfirst)) ||
(method = DSKeyValueMethodForPattern(self, "setIs%s:", key_cstr_upfirst))) {
setter = [[DSKeyValueMethodSetter alloc] initWithContainerClassID:containerClassID key:key method:method];
} else if ([self accessInstanceVariablesDirectly]) {
// Search ivars: _
, _is
,
, is
Ivar ivar = ...;
if (ivar) {
setter = [[DSKeyValueIvarSetter alloc] initWithContainerClassID:containerClassID key:key containerIsa:self ivar:ivar];
}
}
return setter;
}The lookup order for setters is:
Accessor methods: set<Key> , _set<Key> , setIs<Key>
If not found and accessInstanceVariablesDirectly is YES , search ivars: _ , _is<Key> , <key> , is<Key>
If still not found, valueForUndefinedKey: is invoked and an exception is thrown.
Getter Generation
+ (DSKeyValueGetter *)_d_createValueGetterWithContainerClassID:(id)containerClassID key:(NSString *)key {
DSKeyValueGetter *getter = nil;
Method getMethod = NULL;
if ((getMethod = DSKeyValueMethodForPattern(self, "get%s", keyCStrUpFirst)) ||
(getMethod = DSKeyValueMethodForPattern(self, "%s", keyCStr)) ||
(getMethod = DSKeyValueMethodForPattern(self, "is%s", keyCStrUpFirst)) ||
(getMethod = DSKeyValueMethodForPattern(self, "_get%s", keyCStrUpFirst)) ||
(getMethod = DSKeyValueMethodForPattern(self, "_%s", keyCStr))) {
getter = [[DSKeyValueMethodGetter alloc] initWithContainerClassID:containerClassID key:key method:getMethod];
} else if ([self accessInstanceVariablesDirectly]) {
Ivar ivar = ...;
if (ivar) {
getter = [[DSKeyValueIvarGetter alloc] initWithContainerClassID:containerClassID key:key containerIsa:self ivar:ivar];
}
}
if (!getter) {
getter = [self _d_createValuePrimitiveGetterWithContainerClassID:containerClassID key:key];
}
return getter;
}Primitive values are wrapped/unwrapped using NSNumber (for scalar types) or NSValue (for structs). Example conversion methods are listed in two tables:
Data Type
Wrap Method
Unwrap Method
int numberWithInt: intValue float numberWithFloat: floatValue BOOL numberWithBool: boolValueadditional rows omitted for brevity
Data Type
Wrap Method
Unwrap Method
NSPoint valueWithPoint: pointValue NSRange valueWithRange: rangeValue NSRect valueWithRect: rectValueadditional rows omitted for brevity
Primitive Setter Implementation
void _DSSetIntValueForKeyWithMethod(id object, SEL selector, id value, NSString *key, Method method) {
__DSSetPrimitiveValueForKeyWithMethod(object, selector, value, key, method, int, intValue);
}
#define __DSSetPrimitiveValueForKeyWithMethod(object, selector, value, key, method, valueType, valueGetSelectorName) do {\
if (value) {\
void (*imp)(id,SEL,valueType) = (void (*)(id,SEL,valueType))method_getImplementation(method);\
imp(object, method_getName(method), [value valueGetSelectorName]);\
} else {\
[object setNilValueForKey:key];\
}\
} while(0)When the setter is generated, the runtime calls _DSSetUsingKeyValueSetter , which eventually invokes the appropriate IMP (e.g., _DSSetIntValueForKeyWithMethod ) that extracts the primitive value from the NSNumber and calls the original accessor.
Advanced KVC Usage
Batch modify a property of objects in an array: [array setValue:@(YES) forKeyPath:@"selected"];
Aggregate values: NSNumber *sum = [array valueForKeyPath:@"@sum.totalAmount"]; , @avg , @max , @min .
Filter with predicates and then modify: use NSPredicate to obtain selected/unselected arrays and toggle their selected flag.
Practical example in a delivery‑goods management module shows how KVC simplifies updating total delivery, reject, and real delivery amounts, as well as implementing select‑all, clear, and invert operations without explicit loops.
// Model properties
@property (nonatomic, copy) NSString *skuCode;
@property (nonatomic, copy) NSString *goodsName;
@property (nonatomic, assign) NSInteger totalAmount;
@property (nonatomic, assign) NSInteger rejectAmount;
@property (nonatomic, assign) NSInteger deliveryAmount;
@property (nonatomic, assign) BOOL selected;
// Update totals using KVC aggregation
NSNumber *allDeliveryAmount = [self.orderDetailModel.deliveryGoodsDetailList valueForKeyPath:@"@sum.totalAmount"];
NSNumber *allRealDeliveryAmount = [self.orderDetailModel.deliveryGoodsDetailList valueForKeyPath:@"@sum.deliveryAmount"];
NSNumber *allRejectAmount = [self.orderDetailModel.deliveryGoodsDetailList valueForKeyPath:@"@sum.rejectAmount"];
// Select all
[self.orderDetailModel.deliveryGoodsDetailList setValue:@(YES) forKeyPath:@"selected"];
// Clear selection
[self.orderDetailModel.deliveryGoodsDetailList setValue:@(NO) forKeyPath:@"selected"];
// Invert selection using predicates
NSPredicate *selectedPredicate = [NSPredicate predicateWithFormat:@"selected == %@", @(YES)];
NSArray *selectedArray = [self.orderDetailModel.deliveryGoodsDetailList filteredArrayUsingPredicate:selectedPredicate];
NSPredicate *unselectedPredicate = [NSPredicate predicateWithFormat:@"selected == %@", @(NO)];
NSArray *unselectedArray = [self.orderDetailModel.deliveryGoodsDetailList filteredArrayUsingPredicate:unselectedPredicate];
[selectedArray setValue:@(NO) forKeyPath:@"selected"];
[unselectedArray setValue:@(YES) forKeyPath:@"selected"];In summary, KVC’s ability to wrap primitive types with NSNumber / NSValue and its key‑path syntax enable concise, readable, and robust data manipulation, reducing boilerplate loops and improving code maintainability.
JD Tech
Official JD technology sharing platform. All the cutting‑edge JD tech, innovative insights, and open‑source solutions you’re looking for, all in one place.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.