Search code examples
objective-ckey-value-observing

KVC strange behavior


Why this code works fine:

NSArray* arr = @[[CALayer layer], [CALayer layer]];
NSString *sumKeyPath = @"@sum.bounds.size.width";
CGFloat totalSize = [[arr valueForKeyPath:sumKeyPath] floatValue];

But this code give error:

NSArray* arr = @[[UIImage imageNamed:@"img1"], [UIImage imageNamed:@"img2"]];
NSString *sumKeyPath = @"@sum.size.width";
CGFloat totalSize = [[arr valueForKeyPath:sumKeyPath] floatValue];

Error: [NSConcreteValue valueForUndefinedKey:]: this class is not key value coding-compliant for the key width.

NSArray* arr = @[[UIView new], [UIView new]];
NSString *sumKeyPath = @"@sum.bounds.size.width";
CGFloat totalSize = [[arr valueForKeyPath:sumKeyPath] floatValue];

give the same error


Solution

  • CALayer has a special implementation for valueForKeyPath:. For example, the following works:

    CALayer *layer = [CALayer layer];
    id x0 = [layer valueForKeyPath:@"bounds"];
    // --> NSValue object containing a NSRect
    id y0 = [layer valueForKeyPath:@"bounds.size"];
    // --> NSValue object containing a NSSize
    id z0 = [layer valueForKeyPath:@"bounds.size.width"];
    // --> NSNumber object containing a float
    

    But the following does not work:

    CALayer *layer = [CALayer layer];
    id x = [layer valueForKey:@"bounds"];
    // --> NSValue object containing a NSRect
    id y = [x valueForKey:@"size"];
    // --> Exception: '[<NSConcreteValue 0x71189e0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key size.'
    

    So generally, NSValue objects containing a NSRect or NSSize are not key-value compliant. It works only with CALayer because the valueForKeyPath: implementation handles the entire key path, instead of evaluating the first key and passing down the remaining key path.

    UIImage does not have a special implementation for valueForKeyPath:. Therefore

    UIImage *img1 = [UIImage imageNamed:@"img1"];
    id x1 = [img1 valueForKey:@"size"];
    // --> NSValue containing a NSSize
    

    works, but

    UIImage *img1 = [UIImage imageNamed:@"img1"];
    id x1 = [img1 valueForKeyPath:@"size.width"];
    

    does not work.