27

Is there a way construct a predicate to filter by class type?

I currently loop through the array and check to the class of each object. Maybe there is a cleaner way?

Corey Floyd
  • 25,929
  • 31
  • 126
  • 154
  • Why are you putting such different objects into the same array in the first place? – Peter Hosey Apr 02 '10 at 13:58
  • 12
    One reason is if you have an array of abstract classes. You may have all Person classes, but you want to filter out Doctors or Lawyers. – rob5408 Jul 05 '10 at 17:57

3 Answers3

78

You can directly compare classes as well in your predicate.

But, it probably won't work as you would expect if you're trying to filter for objects that belong to class clusters or if you have subclasses.

For example, NSDate when instantiated is usually an __NSCFDate and NSString can be NSCFString as well as other specific private classes.

It's probably better to just loop through the set yourself and use -isKindOfClass: as the test.

IF you really want to use NSPredicate though you can do this. As an example, this would filter an array for all objects derived from NSString. If you wanted strict class membership you could replace isKindOfClass: with isMemberOfClass:.

Any selector that all objects in the collection implement, takes one argument and returns a BOOL should work though.

NSArray *mixedArray = {...};
NSPredicate *predicate = [NSPredicate predicateWithFormat:
                                      @"self isKindOfClass: %@",
                                      [NSString class]];

NSLog(@"%@", [mixedArray filteredArrayUsingPredicate:predicate]);
Ashley Clark
  • 8,813
  • 3
  • 35
  • 35
  • 1
    +1 I've never thought of putting the selector directly in the predicate. Neat! – Dave DeLong Apr 01 '10 at 04:27
  • 2
    It's not mentioned in the Predicate Programming Guide (which means doing this in a format string is technically undocumented), but the custom-selector feature is backed up by the reference: http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSComparisonPredicate_Class/Reference/NSComparisonPredicate.html#//apple_ref/occ/clm/NSComparisonPredicate/predicateWithLeftExpression:rightExpression:customSelector: Very cool. – Peter Hosey Apr 02 '10 at 14:00
  • Yeah, I originally had it written the long way but on a lark tried it in the format string and was surprised to see it work. – Ashley Clark Apr 02 '10 at 16:01
  • The reason why is discouraged is that the predicate translate this to other kinds of predicate statements (like in Core Data by translating it to a SQL statement). – Zac Bowling Nov 01 '11 at 23:30
  • @AshleyClark what's the long way? – ma11hew28 Dec 04 '11 at 19:51
  • @ZacBowling is there an encouraged way to do this? Will this work with Core Data? I have a `linkedAccounts` to-many relationship and want to find the one that is of class `FacebookAccount`. – ma11hew28 Dec 04 '11 at 19:54
  • @MattDiPasquale With core data? It's better to enumerate the NSEntityDescription's attributes and check for NSRelationshipAttribute objects. Then cast and check "isToMany" and the "destinationEntity" properties. If you need more details, ask another question so I can post some sample code and get some loved karama. – Zac Bowling Dec 04 '11 at 20:39
  • @ZacBowling http://stackoverflow.com/questions/9984820/core-data-nspredicate-filter-by-entity-class – ma11hew28 Apr 02 '12 at 22:46
29

Starting in iOS 4 and Mac OS 10.6, one can use +[NSPredicate predicateWithBlock:] as well. For example:

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

This allows you to express your predicates purely in Objective-C rather than the predicate syntax required by predicateWithFormat:.

Carl Veazey
  • 18,392
  • 8
  • 66
  • 81
24

You could add a category to NSObject that adds a "cf_className" method, like so:

@interface NSObject (CFAdditions)
- (NSString *) cf_className;
@end

@implementation NSObject (CFAdditions)
- (NSString *) cf_className {
  return NSStringFromClass([self class]);
}
@end

From there, you can use predicates like:

NSPredicate * p = [NSPredicate predicateWithFormat:@"cf_className = %@", aClass];
NSArray * filtered = [anArray filteredArrayUsingPredicate:p];

If you're on the Mac, you can just use -[NSObject className] instead of having to create the category. The iPhone doesn't have that method, hence the need for a category.

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498