Can I use NSPredicate as key in a NSDictionary

441 views Asked by At

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.

3

There are 3 answers

2
Martin R On BEST ANSWER

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.

2
AudioBubble On

Yes, you can use any object as a key as long as its class implements NSCopying.

0
L__ On

Unfortunately it doesn't work as I expected. Although NSPredicate implements NSCopying, the fact that a predicate can contain an arbitrary block should make it obvious that it would be virtually impossible to compare two NSPredicates reliably.

Tested on my machine, even the same predicate instance used to query the dictionary won't return the previous set value. My guess is that when the dictionary takes the predicate it copies it, and because the implementation of isEqual: for NSPredicate probably only returns true on equal pointers the original predicate instance compared to the its copy also returns false.