3

I have a class with a property definition like so:

// Interface
@property (retain) __attribute__((NSObject)) CGImageRef thumbnailImage;

// Implementation
@synthesize thumbnailImage;

The problem is that self.thumbnailImage = newCGImageRef; does not cause it to fire a key/value observation event and observeValueForKeyPath:... is never called.

I tested the code with another property (BOOL) of the same class and it works just fine for that one. Shouldn't this also work for the property above?

Note: I tried setting the property manually ([self setValue:newValue forKey:@"thumbnailImage"], which resulted in the error below:

setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key thumbnailImage.
Mark
  • 6,647
  • 1
  • 45
  • 88
  • Why do you use __attribute__((NSObject))? Do you @synthesize or implement the accessors yourself? Do you override automaticallyNotifiesObserversForKey:? – an0 Sep 09 '11 at 15:43
  • @an0 the `__attribute__((NSObject))` is required to treat the the foundation type as on object so that it can be retained. With out it you will get a compiler error. – Joe Sep 09 '11 at 15:47
  • 2
    @joe, but is CGImageRef a Core Foundation object? I think it is not a CFType and CFRelease/CFRetain are not the proper memory management functions for it — CGImageRef has its own CGImageRetain/CGImageRelease. – an0 Sep 09 '11 at 19:13
  • @an0 CGImageRef is Core Graphics but it is still a struct pointer that can be treated as an `NSObject` it just can not be cast directly to a `UIImage` since it is not toll free bridged. The attribute will work and will still retain it and release it properly. http://stackoverflow.com/questions/1263642/releasing-cgimage-cgimageref – Joe Sep 09 '11 at 19:43
  • @Joe CFRelease/CFRetain are not substitutes for CGImageRetain/CGImageRelease. I don't think we should make any assumption of the implementation of CGImageRetain/CGImageRelease. We should treat them as black boxes. – an0 Sep 10 '11 at 04:57

1 Answers1

4

__attribute__((NSObject)) tells the compiler to treat structure pointers as an object therefore this allows you to retain/release the object (often used for blocks). This attribute has no effect on the fact that CFImageRef is a struct pointer and KVC does not automatically wrap struct pointers though it will wrap structs. To solve your issue you will need to intercept KVC on undefined keys to set and return the correct object, or just use UIImage instead.

KVC Documentation:

Automatic wrapping and unwrapping is not confined to NSPoint, NSRange, NSRect, and NSSize—structure types (that is, types whose Objective-C type encoding strings start with {) can be wrapped in an NSValue object.

TestCF_KVC.h

#import <Foundation/Foundation.h>

typedef struct
{
    int x, y, z;
} AStruct;

@interface TestCF_KVC : NSObject {}

@property (nonatomic, retain) NSString *text;
@property (nonatomic, assign) AStruct aStruct;
@property (nonatomic, retain) __attribute__((NSObject)) CFStringRef cftext;

-(void)test;

@end

TestCF_KVC.m

#import "TestCF_KVC.h"

@implementation TestCF_KVC

@synthesize text, aStruct, cftext;

-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
    NSLog(@"undefined key set for %@", key);
    if([key isEqualToString:@"cftext"])
    {
        self.cftext = (CFStringRef)value;
    }
    else
    {
        [super setValue:value forUndefinedKey:key];
    }
}

-(id)valueForUndefinedKey:(NSString *)key
{
    NSLog(@"undefined key get for %@", key);
    if([key isEqualToString:@"cftext"])
    {
        return (NSString*)self.cftext;
    }

    return [super valueForUndefinedKey:key];
}

-(void)test
{
    NSString *txt = @"text worked";
    AStruct astr = { .x=1, .y=5, .z=10 };
    CFStringRef cftxt = (CFStringRef)@"cftext worked";

    //Test a normal NSString for KVC
    [self setValue:txt forKey:@"text"];
    txt = [self valueForKey:@"text"];
    NSLog(@"text[%s]: %@", @encode(NSString), [self valueForKey:@"text"]);

    //Test a struct for KVC
    NSValue *value = [NSValue value:&astr withObjCType:@encode(AStruct)];
    [self setValue:value forKey:@"aStruct"];
    [[self valueForKey:@"aStruct"] getValue:&astr];
    NSLog(@"aStruct[%s]: %d %d %d", @encode(AStruct), aStruct.x, aStruct.y, aStruct.z);

    //Test a Core Foundation type for KVC
    [self setValue:(NSString*)cftxt forKey:@"cftext"];
    cftxt = (CFStringRef)[self valueForKey:@"cftext"];
    NSLog(@"cftext[%s]: %@", @encode(CFStringRef), (NSString*)cftxt);
}

@end

Log Output From Calling -test:

text[{NSString=#}]: text worked
aStruct[{?=iii}]: 1 5 10
undefined key set for cftext
undefined key get for cftext
cftext[^{__CFString=}]: cftext worked
Joe
  • 56,979
  • 9
  • 128
  • 135
  • Thank you for this excellent explanation. Just one more question: will the synthesized setter retain the CFImageRef instance? I assumed it would, but after you said it treats it as a struct I'm not sure. How would the compiler know how to retain a struct? – Mark Sep 09 '11 at 16:55
  • @Joe, good demo. Do you know any official doc about KVO for non-NSObject objects such as CF and CG objects? – an0 Sep 09 '11 at 19:17
  • @an0 I was looking for some and was hoping someone could post it. All I found was the documentation stating that KVC supports any struct types that begin with `{` when `@encode`ed – Joe Sep 09 '11 at 19:20
  • @Joe, I also only found that. But when the doc says "struct" it does mean "struct", which is obviously not applied to "struct pointer". It seems we need some Apple guys for help. – an0 Sep 09 '11 at 19:24