NSPredicate isKindOfClass only works for [myObject

2019-02-25 21:53发布

问题:

Why is it that [MyClass class] fails to find the objects of the class I want in a predicate whereas [myObject class] works? See code example below. I want to be able to find objects of type MyClass in a set without needing to have a spurious instance of the class available.

MyClass* myObject = [[MyClass alloc] init];

This doesn't work...

NSPredicate* predicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [MyClass class]];
NSArray* predicateResults = [mySet filteredSetUsingPredicate:predicate].allObjects;

This does work...

NSPredicate* predicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [myObject class]];
NSArray* predicateResults = [mySet filteredSetUsingPredicate:predicate].allObjects;

回答1:

A predicate like

[NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [MyClass class]]

actually works. It returns exactly the same predicate as the (documented)

[NSComparisonPredicate
    predicateWithLeftExpression:[NSExpression expressionForEvaluatedObject]
                rightExpression:[NSExpression expressionForConstantValue:[MyClass class]]
                 customSelector:@selector(isMemberOfClass:)]

The problem in your case could be that isMemberOfClass: compares for class equality, so this would not match objects which are members of a subclass of MyClass.

This could happen if MyClass is a class cluster and myObject is an instance of a concrete subclass. For example:

NSString *myString = [[NSString alloc] initWithUTF8String:"hello world"];
Class c1 = [NSString class];   //  NSString
Class c2 = [myString class];   //  __NSCFString

BOOL b1 = [myString isMemberOfClass:c1]; // NO
BOOL b2 = [myString isMemberOfClass:c2]; // YES

In that case [myObject class] and [MyClass class] are different, which would explain why one predicate works and the other predicate does not work.

The following code uses isKindOfClass: instead and produces the expected result:

NSArray *array = @[@"123", @456, [NSArray array]];
NSPredicate* predicate = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", [NSNumber class]];
NSArray* predicateResults = [array filteredArrayUsingPredicate:predicate];
NSLog(@"%@", predicateResults);
//  Output: ( 456 )


回答2:

Not strictly speaking an answer to your question (see MartinR excellent answer), but an alternative to the (IMO) under documented predicateWithFormat: method:

You could use predicateWithBlock: to create your NSPredicate, like so:

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

This should work fine.