Search code examples
objective-ckvc

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 NSNulls.

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


Solution

  • 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".