NSPredicate
NSPredicate
is a Foundation class that specifies how data should be fetched or filtered. Its query language, which is like a cross between a SQL WHERE
clause and a regular expression, provides an expressive, natural language interface to define logical conditions on which a collection is searched.
It’s easier to show NSPredicate
in use, rather than talk about it in the abstract, so we’re going to revisit the example data set used in the NSSort
article:
first |
last |
age |
---|---|---|
Alice | Smith | 24 |
Bob | Jones | 27 |
Charlie | Smith | 33 |
Quentin | Alberts | 31 |
@objc Members class Person: NSObject {
let first Name: String
let last Name: String
let age: Int
init(first Name: String, last Name: String, age: Int) {
self.first Name = first Name
self.last Name = last Name
self.age = age
}
override var description: String {
return "\(first Name) \(last Name)"
}
}
let alice = Person(first Name: "Alice", last Name: "Smith", age: 24)
let bob = Person(first Name: "Bob", last Name: "Jones", age: 27)
let charlie = Person(first Name: "Charlie", last Name: "Smith", age: 33)
let quentin = Person(first Name: "Quentin", last Name: "Alberts", age: 31)
let people = [alice, bob, charlie, quentin] as NSArray
let bob Predicate = NSPredicate(format: "first Name = 'Bob'")
let smith Predicate = NSPredicate(format: "last Name = %@", "Smith")
let thirties Predicate = NSPredicate(format: "age >= 30")
people.filtered(using: bob Predicate)
// ["Bob Jones"]
people.filtered(using: smith Predicate)
// ["Alice Smith", "Charlie Smith"]
people.filtered(using: thirties Predicate)
// ["Charlie Smith", "Quentin Alberts"]
@interface Person : NSObject
@property NSString *first Name;
@property NSString *last Name;
@property NSNumber *age;
@end
@implementation Person
- (NSString *)description {
return [NSString string With Format:@"%@ %@", self.first Name, self.last Name];
}
@end
#pragma mark -
NSArray *first Names = @[ @"Alice", @"Bob", @"Charlie", @"Quentin" ];
NSArray *last Names = @[ @"Smith", @"Jones", @"Smith", @"Alberts" ];
NSArray *ages = @[ @24, @27, @33, @31 ];
NSMutable Array *people = [NSMutable Array array];
[first Names enumerate Objects Using Block:^(id obj, NSUInteger idx, BOOL *stop) {
Person *person = [[Person alloc] init];
person.first Name = first Names[idx];
person.last Name = last Names[idx];
person.age = ages[idx];
[people add Object:person];
}];
NSPredicate *bob Predicate = [NSPredicate predicate With Format:@"first Name = 'Bob'"];
NSPredicate *smith Predicate = [NSPredicate predicate With Format:@"last Name = %@", @"Smith"];
NSPredicate *thirties Predicate = [NSPredicate predicate With Format:@"age >= 30"];
// ["Bob Jones"]
NSLog(@"Bobs: %@", [people filtered Array Using Predicate:bob Predicate]);
// ["Alice Smith", "Charlie Smith"]
NSLog(@"Smiths: %@", [people filtered Array Using Predicate:smith Predicate]);
// ["Charlie Smith", "Quentin Alberts"]
NSLog(@"30's: %@", [people filtered Array Using Predicate:thirties Predicate]);
NSPredicate
with Collections
Using Foundation provides methods to filter NSArray
/ NSMutable
& NSSet
/ NSMutable
with predicates.
Immutable collections, NSArray
& NSSet
, have the methods filtered
and filtered
which return an immutable collection by evaluating a predicate on the receiver.
Mutable collections, NSMutable
& NSMutable
have the method filter
, which removes any objects that evaluate to FALSE
when running the predicate on the receiver.
NSDictionary
can use predicates by filtering its keys or values (both NSArray
objects). NSOrdered
can either create new ordered sets from a filtered NSArray
or NSSet
, or alternatively, NSMutable
can simply remove
, passing objects filtered with the negated predicate.
NSPredicate
with Core Data
Using NSFetch
has a predicate
property, which specifies the logical conditions under which managed objects should be retrieved. The same rules apply, except that predicates are evaluated by the persistent store coordinator within a managed object context, rather than collections being filtered in-memory.
Predicate Syntax
Substitutions
%@
is a var arg substitution for an object value—often a string, number, or date.%K
is a var arg substitution for a key path.
let age Is33Predicate = NSPredicate(format: "%K = %@", "age", "33")
people.filtered(using: age Is33Predicate)
// ["Charlie Smith"]
NSPredicate *age Is33Predicate = [NSPredicate predicate With Format:@"%K = %@", @"age", @33];
// ["Charlie Smith"]
NSLog(@"Age 33: %@", [people filtered Array Using Predicate:age Is33Predicate]);
$VARIABLE_NAME
is a value that can be substituted withNSPredicate -predicate
.With Substitution Variables:
let names Beginning With Letter Predicate = NSPredicate(format: "(first Name BEGINSWITH[cd] $letter) OR (last Name BEGINSWITH[cd] $letter)")
people.filtered(using: names Beginning With Letter Predicate.with Substitution Variables(["letter": "A"]))
// ["Alice Smith", "Quentin Alberts"]
NSPredicate *names Beginning With Letter Predicate = [NSPredicate predicate With Format:@"(first Name BEGINSWITH[cd] $letter) OR (last Name BEGINSWITH[cd] $letter)"];
// ["Alice Smith", "Quentin Alberts"]
NSLog(@"'A' Names: %@", [people filtered Array Using Predicate:[names Beginning With Letter Predicate predicate With Substitution Variables:@{@"letter": @"A"}]]);
Basic Comparisons
=
,==
: The left-hand expression is equal to the right-hand expression.>=
,=>
: The left-hand expression is greater than or equal to the right-hand expression.<=
,=<
: The left-hand expression is less than or equal to the right-hand expression.>
: The left-hand expression is greater than the right-hand expression.<
: The left-hand expression is less than the right-hand expression.!=
,<>
: The left-hand expression is not equal to the right-hand expression.BETWEEN
: The left-hand expression is between, or equal to either of, the values specified in the right-hand side. The right-hand side is a two value array (an array is required to specify order) giving upper and lower bounds. For example,1 BETWEEN { 0 , 33 }
, or$INPUT BETWEEN { $LOWER, $UPPER }
.
Basic Compound Predicates
AND
,&&
: LogicalAND
.OR
,||
: LogicalOR
.NOT
,!
: LogicalNOT
.
String Comparisons
String comparisons are by default case and diacritic sensitive. You can modify an operator using the key characters c and d within square braces to specify case and diacritic insensitivity respectively, for example firstName BEGINSWITH[cd] $FIRST_NAME.
BEGINSWITH
: The left-hand expression begins with the right-hand expression.CONTAINS
: The left-hand expression contains the right-hand expression.ENDSWITH
: The left-hand expression ends with the right-hand expression.LIKE
: The left hand expression equals the right-hand expression:?
and*
are allowed as wildcard characters, where?
matches 1 character and*
matches 0 or more characters.MATCHES
: The left hand expression equals the right hand expression using a regex-style comparison according to ICU v3 (for more details see the ICU User Guide for Regular Expressions).
Aggregate Operations
Relational Operations
ANY
,SOME
: Specifies any of the elements in the following expression. For example,ANY children.age < 18
.ALL
: Specifies all of the elements in the following expression. For example,ALL children.age < 18
.NONE
: Specifies none of the elements in the following expression. For example,NONE children.age < 18
. This is logically equivalent toNOT (ANY ...)
.IN
: Equivalent to an SQLIN
operation, the left-hand side must appear in the collection specified by the right-hand side. For example,name IN { 'Ben', 'Melissa', 'Nick' }
.
Array Operations
array[index]
: Specifies the element at the specified index inarray
.array[FIRST]
: Specifies the first element inarray
.array[LAST]
: Specifies the last element inarray
.array[SIZE]
: Specifies the size ofarray
.
Boolean Value Predicates
TRUEPREDICATE
: A predicate that always evaluates toTRUE
.FALSEPREDICATE
: A predicate that always evaluates toFALSE
.
NSCompound Predicate
We saw that AND
& OR
can be used in predicate format strings to create compound predicates. However, the same can be accomplished using an NSCompound
.
For example, the following predicates are equivalent:
NSCompound Predicate(
type: .and,
subpredicates: [
NSPredicate(format: "age > 25"),
NSPredicate(format: "first Name = %@", "Quentin")
]
)
NSPredicate(format: "(age > 25) AND (first Name = %@)", "Quentin")
[NSCompound Predicate and Predicate With Subpredicates:@[[NSPredicate predicate With Format:@"age > 25"], [NSPredicate predicate With Format:@"first Name = %@", @"Quentin"]]];
[NSPredicate predicate With Format:@"(age > 25) AND (first Name = %@)", @"Quentin"];
While the syntax string literal is certainly easier to type, there are occasions where you may need to combine existing predicates. In these cases, NSCompound
& -or
is the way to go.
NSComparison Predicate
Similarly, if after reading last week’s article you now find yourself with more NSExpression
objects than you know what to do with, NSComparison
can help you out.
Like NSCompound
, NSComparison
constructs an NSPredicate
from subcomponents—in this case, NSExpression
s on the left and right hand sides.
Analyzing its class constructor provides a glimpse into the way NSPredicate
format strings are parsed:
+ (NSPredicate *)predicate With Left Expression:(NSExpression *)lhs
right Expression:(NSExpression *)rhs
modifier:(NSComparison Predicate Modifier)modifier
type:(NSPredicate Operator Type)type
options:(NSUInteger)options
init(left Expression lhs: NSExpression,
right Expression rhs: NSExpression,
custom Selector selector: Selector)
Parameters
lhs
: The left hand expression.rhs
: The right hand expression.modifier
: The modifier to apply. (ANY
orALL
)type
: The predicate operator type.options
: The options to apply. For no options, pass0
.
NSComparison Predicate
Types
enum NSComparison Predicate.Operator: UInt {
case less Than
case less Than Or Equal To
case greater Than
case greater Than Or Equal To
case equal To
case not Equal To
case matches
case like
case begins With
case ends With
case `in`
case custom Selector
case contains
case between
}
enum {
NSLess Than Predicate Operator Type = 0,
NSLess Than Or Equal To Predicate Operator Type,
NSGreater Than Predicate Operator Type,
NSGreater Than Or Equal To Predicate Operator Type,
NSEqual To Predicate Operator Type,
NSNot Equal To Predicate Operator Type,
NSMatches Predicate Operator Type,
NSLike Predicate Operator Type,
NSBegins With Predicate Operator Type,
NSEnds With Predicate Operator Type,
NSIn Predicate Operator Type,
NSCustom Selector Predicate Operator Type,
NSContains Predicate Operator Type,
NSBetween Predicate Operator Type
};
typedef NSUInteger NSPredicate Operator Type;
NSComparison Predicate
Options
NSCase
: A case-insensitive predicate. You represent this option in a predicate format string using a [c] following a string operation (for example,Insensitive Predicate Option "Ne
).XT" like[c] "next" NSDiacritic
: A diacritic-insensitive predicate. You represent this option in a predicate format string using a [d] following a string operation (for example,Insensitive Predicate Option "naïve" like[d] "naive"
).NSNormalized
: Indicates that the strings to be compared have been preprocessed. This option supersedes NSCaseInsensitivePredicateOption and NSDiacriticInsensitivePredicateOption, and is intended as a performance optimization option. You represent this option in a predicate format string using a [n] following a string operation (for example,Predicate Option "WXYZlan" matches[n] ".lan"
).NSLocale
: Indicates that strings to be compared usingSensitive Predicate Option <
,<=
,=
,=>
,>
should be handled in a locale-aware fashion. You represent this option in a predicate format string using a[l]
following one of the<
,<=
,=
,=>
,>
operators (for example,"straße" >[l] "strasse"
).
Block Predicates
Finally, if you just can’t be bothered to learn the NSPredicate
format syntax, you can go through the motions with NSPredicate +predicate
.
let short Name Predicate = NSPredicate { (evaluated Object, _) in
return (evaluated Object as! Person).first Name.utf16.count <= 5
}
people.filtered(using: short Name Predicate)
// ["Alice Smith", "Bob Jones"]
NSPredicate *short Name Predicate = [NSPredicate predicate With Block:^BOOL(id evaluated Object, NSDictionary *bindings) {
return [[evaluated Object first Name] length] <= 5;
}];
// ["Alice Smith", "Bob Jones"]
NSLog(@"Short Names: %@", [people filtered Array Using Predicate:short Name Predicate]);
…Alright, that whole dig on predicate
as being the lazy way out wasn’t entirely charitable.
Actually, since blocks can encapsulate any kind of calculation, there is a whole class of queries that can’t be expressed with the NSPredicate
format string (such as evaluating against values dynamically calculated at run-time). And while its possible to accomplish the same using an NSExpression
with a custom selector, blocks provide a convenient interface to get the job done.
One important note: NSPredicate
s created with predicate
cannot be used for Core Data fetch requests backed by a SQLite
store.
NSPredicate
is, and I know this is said a lot, truly one of the jewels of Cocoa. Other languages would be lucky to have something with half of its capabilities in a third-party library—let alone the standard library. Having it as a standard-issue component affords us as application and framework developers an incredible amount of leverage in working with data.
Together with NSExpression
, NSPredicate
reminds us what a treat Foundation is: a framework that is not only incredibly useful, but meticulously architected and engineered, to be taken as inspiration for how we should write our own code.