cxa cxa - 6 months ago 19
Objective-C Question

Problems on NSArray's -valueForKey: when its item is NSDictionary

I have an array which contains items of

NSDictionary
, I want to transform the items to other objects, my first thought is
valueForKey:
, so I add a category method
toMyObject
for
NSDictionary
, and call for:

[array valueForKey:@"toMyObject"]


But it doesn't work as expect, it just returns the array of
NSNull
s.

Any ideas to solve this problem if I don't want to enumerate the array?

cxa cxa
Answer

Answer to myself. The valueForKey: of dictionary overwrite the default behavior, if the dictionary doesn't have the key, it will return nil and not call the accessor method as NSObject do, as Apple document says:

If key does not start with “@”, invokes objectForKey:. If key does start with “@”, strips the “@” and invokes [super valueForKey:] with the rest of the key.

Since NSDictionary is a cluster class, it's not recommend to subclass to overwrite the behavior. Instead I use the method swiss like this:

@implementation NSDictionary (MyAddition)

static void swizzle(Class c, SEL orig, SEL new)
{
  Method origMethod = class_getInstanceMethod(c, orig);
  Method newMethod = class_getInstanceMethod(c, new);
  if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
    class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
  else
    method_exchangeImplementations(origMethod, newMethod);
}

+ (void)initialize
{
  if (self == [NSDictionary class]){
    swizzle([NSDictionary class],
            @selector(valueForKey:),
            @selector(myValueForKey:));
  }
}

- (id)toMyObject
{
  return toMyObject;
}

...

- (id)myValueForKey:(NSString *)key
{
  // for collection operators
  if ([key compare:@"@" options:0 range:NSMakeRange(0, 1)] == NSOrderedSame)
    return [super valueForKey:key];

  if ([key isEqualToString:@"toMyObject"])
    return [self toMyObject];

  return [self myValueForKey:key];
}

Now it's safe for an NSArray to call valueForKey:@"toMyObject".