Fundamentals 15 min read

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.

JD Tech
JD Tech
JD Tech
Understanding Key‑Value Coding (KVC) in Objective‑C: Principles, Execution Flow, and Advanced Applications

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:
boolValue

additional rows omitted for brevity

Data Type

Wrap Method

Unwrap Method

NSPoint
valueWithPoint:
pointValue
NSRange
valueWithRange:
rangeValue
NSRect
valueWithRect:
rectValue

additional 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.

iOSruntimeObjective-CData Bindingadvanced usageKey-Value CodingKVCNSValue
JD Tech
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.