0

I need to override the valueForKey: method of a NSMutableDictionary. I want to check for a specific value, do some manipulation and return it. Are there any points that should I be aware of ? Please correct me if there is something wrong, for example:

- (id)valueForKey:(NSString*)key {

    id val = [super valueForKey:key];
    if([val isKindOfClass:[NSString class]] && [val isEqualToString:@"<null>"]) {
        return @"No Value";
    }
    else {
        return val;
    }

}

Thanks

Vassilis
  • 2,878
  • 1
  • 28
  • 43
  • use Categories the proper way to do this http://mobile.tutsplus.com/tutorials/iphone/learn-objective-c-day-6/ – Badr Apr 21 '11 at 15:26
  • @moon: the problem with categories is that you can't cleanly call the original implementation of anything you replace; see http://stackoverflow.com/questions/1085479/override-a-method-via-objc-category-and-call-the-default-implementation – Tommy Apr 21 '11 at 15:28
  • This sounds like a job for a data transformer, actually. – NSResponder Apr 21 '11 at 15:35
  • xm indeed, but I also use them with IB bindings, TableViews, etc and sounds that I'd have more code to replace this way – Vassilis Apr 21 '11 at 15:49

3 Answers3

8

You probably don't want to override valueForKey, because NSDictionary and NSMutableDictionary are abstract base classes hiding a class cluster behind them. Per their documentation, in a straight subclassing you'd need to reimplement all of:

  • setObject:forKey:
  • removeObjectForKey:
  • count
  • objectForKey:
  • keyEnumerator

It's easier to implement an alternative class that pretends to be an NSMutableDictionary but forwards any calls it doesn't understand to an actual NSMutableDictionary. Which you can do via forwardingTargetForSelector:.

E.g.

@interface MyFakeDictionary: NSObject
{
    NSMutableDictionary *theRealDictionary;
}

/* reimplement whatever init methods you want to use */

- (id)valueForKey:(id)key;

@end

...

@implementation MyFakeDictionary

- (id)valueForKey:(id)key
{
   id val = [theRealDictionary objectForKey:key];
   /* etc, as you like */
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return theRealDictionary;
}

@end

So there's no inheritance relationship between MyFakeDictionary and NSMutableDictionary, but if you send a selector to MyFakeDictionary that it doesn't understand (like any of the normal dictionary messages other than the one you've replaced), NSObject's built-in logic will redirect them to the member theRealDictionary. It's 'has a' rather than 'is a', with transparent message forwarding, and therefore conveniently dodges any issues with class clusters.

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • Of course I need to alloc it and release it in -init & -dealloc yeh? Sounds the best solution! But I've always be using @interface ... to declare classes, etc. The @class... make the compiler give me `... error: expected ';' before '{' token` ?? What am I missing ? – Vassilis Apr 21 '11 at 15:44
  • You're not missing anything, my brain was awry. You use @class for declaring that interfaces exist but not defining them, @interface for defining them. You're quite right to use @interface, I'm quite wrong to have used @class in my initial answer and I'll correct it immediately. And: yes, you'd need to create an NSMutableDictionary for the variable 'theRealDictionary' to reference in an init, release it in a dealloc. You'd actually need to reimplement the inits that you want so it you wanted 'initWithObjectsAndKeys:' you'd have to create the dictionary and then push the objects into it. – Tommy Apr 21 '11 at 15:50
  • Thanks @Tommy, I' ll get back if the hall thing worked for my case. – Vassilis Apr 21 '11 at 15:58
  • xm, I' ll have to spend some time on this one, I take many warnings, errors like ... `does not implement methodSignatureForSelector:`, etc issues. Still seems the right solution though... . Maybe I'll try a solution with NSValueTransformer witch might be best in my case. Thank you... – Vassilis Apr 21 '11 at 16:28
  • Sorry; just another error from coding extemporaneously: your object should inherit from NSObject. I'll correct my answer. – Tommy Apr 21 '11 at 18:03
0

You must pay attention when subclassing NSMutableDictionary:

from the Apple docs:

Methods to Override

In a subclass, you must override both
of its primitive methods:

setObject:forKey: 
removeObjectForKey:
You must also override the primitive methods of the NSDictionary class.
Joris Mans
  • 6,024
  • 6
  • 42
  • 69
0

Take your code and wrap it in the following:

@implementation NSMutableDictionary(myValueForKey)

<your code here>

@end

This is a category on NSMutableDictionary. Categories make it easy to add to or augment standard methods without having to subclass.

One recommendation would be to use a different method name, though, since you won't want to call [super valueForKey:...] -- you'll want to call [self valueForKey:...]. So using a different method name for your method will differentiate yours from the standard method.

Mark Granoff
  • 16,878
  • 2
  • 59
  • 61
  • I use IB bindings, so I want valueForKey: to be "overridden", to be functional with bindings too. So do I call [super ...] for default implementation or [self ...] in case of category ? Thank you – Vassilis Apr 21 '11 at 15:30
  • With a category, you are extending a class, so when category method are called, `self` refers to the object (the receiver) on which the method is being called. So if you create a category method, say `myValueForKey:`, in that method `self` refers to the object on which you called `myValueForKey:`, and you can then call `[self valueForKey:...]` to get at the orginal class's `valueForKey:` method. This is different from subclassing a class and overriding a base class method. In this case, you would access the base class functionality by calling the original method on `super`. – Mark Granoff Apr 21 '11 at 15:53