Domanda

I have an object that is likely to have NSNull set as a value on all of its properties, and I'm working with code that doesn't play nice with NSNull values. Is it possible to override setValue: forKey: in my object like this?

-(void)setValue:(id)value forKey:(NSString *)key {

    if ([value isEqual:[NSNull null]]) {
        [super setValue:nil forKey:key];
    } else {
        [super setValue:value forKey:key];
    }
}

The method doesn't get called when object.property = [NSNull null] gets set on it. How can I make this the default behavior for my setters?

È stato utile?

Soluzione

There are definitely ways to do this. You could declare the relevant properties @dynamic and then use +resolveInstanceMethod: to actually create the setter on-demand. You can find an example like this in the sample code for iOS:PTL. This example demonstrates how to automatically make your accessors read and write to a dictionary (properties) rather than from ivars. To make this work for you, you'd probably need to always store NSNull in the dictionary and override the getter (propertyIMP()) to convert it to nil. So you'd change this:

[[self properties] setValue:value forKey:key];

to:

[[self properties] setValue:(value ?: [NSNull null]) forKey:key];

And change:

return [[self properties] valueForKey:NSStringFromSelector(_cmd)];

to:

id value = [[self properties] valueForKey:NSStringFromSelector(_cmd)];
return (value == [NSNull null] ? nil : value);

Or something like that.

BUT... unless this is a major win, I would avoid this kind of magic. My typical solution to this is to go the other way, and put it on the caller not to pass [NSNull null] when they shouldn't. I use a function like RNNonNull():

id RNNonNull(id x) { return (x == [NSNull null]) ? nil : x; }

Then the caller is responsible for adding the wrapper:

obj.foo = RNNonNull(mystuff);

If that's not possible, I'd probably override the getters by hand to convert NSNull to nil rather than doing it with the runtime (it'd be a one-line method). The only reason I'd use the runtime is if there were a bunch of properties, and the object were very simple (purely a data object).

Altri suggerimenti

Setting a property does not actually result in a call to setValue:forKey:. You could accomplish this with KVO. For example,

@interface Foo
@property (nonatomic, strong) NSObject *bar;
@end

@implementation

- (id)init {
    self = [super init];
    if (self) {
        [self addObserver:self forKeyPath:@"bar" options:0 context:nil];
    }
    return self;
}

- (void)dealloc {
    [self removeObserver:self forKeyPath:@"bar"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (self.bar == [NSNull null]) {
        self.bar = nil;
    }
}

So any time the property bar is set to [NSNull null it will immediately be set to nil.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top