Search code examples
objective-cnsdictionarynspredicatenscopying

Can I use NSPredicate as key in a NSDictionary


Say I have two NSPredicates

NSPredicate *pa = [NSPredicate predicateWithBlock:^(BOOL)(id evaluatedObject, NSDictionary *bindings) {
    return [evaluatedObject isKindOfClass:[TestClass class]];
}];

NSPredicate *pb = [NSPredicate predicateWithBlock:^(BOOL)(id evaluatedObject, NSDictionary *bindings) {
    return [evaluatedObject isKindOfClass:NSClassFromString(@"TestClass")];
}];

What I want is that when I put pa in a NSDictionary and associate it with another object, say obj, later when I check in the dictionary using pb I get back obj.

Is that how it works?

I see that NSPredicate implements NSCopying, so I'm hoping that it works as a key. But I'm not sure about the case I outlined above.


Solution

  • NSPredicate does override isEqual, but this seems to be useful only in very simple cases:

    NSPredicate *pa = [NSPredicate predicateWithFormat:@"foo = 'bar'"];
    NSPredicate *pb = [NSPredicate predicateWithFormat:@"foo = 'bar'"];
    BOOL b = [pa isEqual:pb]; // --> YES
    

    In this case you could put an object into a dictionary using key pa and get the object back using key pb.

    But this does not work with block-based predicates at all, even if they reference the same block:

    BOOL (^block)(id evaluatedObject, NSDictionary *bindings) = ^BOOL(id evaluatedObject, NSDictionary *bindings) {
        return [evaluatedObject isKindOfClass:[NSString class]];
    };
    NSPredicate *pa = [NSPredicate predicateWithBlock:block];
    NSPredicate *pb = [NSPredicate predicateWithBlock:block];
    BOOL b = [pa isEqual:pb]; // --> NO
    

    And even if that would work, it would require in your case that the two blocks in the predicate are recognized as equal, which is not the case:

    BOOL (^block1)(id evaluatedObject, NSDictionary *bindings) = ^BOOL(id evaluatedObject, NSDictionary *bindings) {
        return [evaluatedObject isKindOfClass:[NSString class]];
    };
    BOOL (^block2)(id evaluatedObject, NSDictionary *bindings) = ^BOOL(id evaluatedObject, NSDictionary *bindings) {
        return [evaluatedObject isKindOfClass:NSClassFromString(@"NSString")];
    };
    BOOL b = [block1 isEqual:block2]; // --> NO
    

    Finally it turns out that two blocks with the same body are not equal:

    BOOL (^block1)(id evaluatedObject, NSDictionary *bindings) = ^BOOL(id evaluatedObject, NSDictionary *bindings) {
        return [evaluatedObject isKindOfClass:[NSString class]];
    };
    BOOL (^block2)(id evaluatedObject, NSDictionary *bindings) = ^BOOL(id evaluatedObject, NSDictionary *bindings) {
        return [evaluatedObject isKindOfClass:[NSString class]];
    };
    BOOL b = [block1 isEqual:block2]; // --> NO
    

    So you can use a predicates as a key in a dictionary, but at least for block-based predicates this is pretty useless.