Objective-C中的NSPredicate

编写软件时,经常需要获取一个对象集合,并通过某些已经条件计算该集合的值。你需要保留符合某个条件的对象,删除那些不满足条件的对象,从而提供一些有意义的对象。

在使用软件iPhoto的过程中,经常会看到这种现象,如果通知iPhoto仅显示等级为三星级或三星级以上的图片,则指定的条件为“照片的等级必须为三星级或三星级以上”。这样,所有照片都需要经过该过滤器过滤。满足条件的对象通过了过滤器,而其他对象被筛除了。最后,iPhoto将显示出所有高质量的图片。

Cocoa提供了一个名为NSPredicate的类,它用于指定过滤器的条件。可以创建NSPredicate对象,通过该对象准确地描述所需的条件,对每个对象通过谓词进行筛选,判断它们是否与条件相匹配。这里的“谓词”通常用在数学和计算机科学概念中,表示计算真值或假值的函数。

Cocoa用NSPredicate描述查询的方式,原理类似于在数据库中进行查询。可以在数据库风格的API中使用NSPredicate类,例如Core Data和Spotlight。可以将NSPredicate看成另一种间接操作方式。例如,如果需要查询满足条件的机器人,可以使用谓词对象进行检查,而不必使用代码进行显示查询。通过交换谓词对象,可以使用通用代码对数据进行过滤,而不必对相关条件进行硬编码。

创建

1)方式一

创建许多对象,并将它们组合起来。如果正在构建通用用户接口来指定查询,采用这种方式比较简单。

2)方式二

查询代码中的字符串。

1
2
3
4
5
6
7
Car *car;
Car = makeCar(@"Herbie", @"Honda", @"CRX", 1984, 2, 110000, 58);
[garage addCar:car];
/*构建的汽车:品牌为Herbie,型号为双门1984Honda CRX,马力引擎为58,已经行驶距离为110000英里*/
/*创建谓词*/
NSPredicate *predicate;
predicate = [NSPredicate predicateWithFormat:@"name == 'Herbie'"];

计算谓词

1
2
BOOL match = [predicate evaluateWithObject:car];
NSLog(@"%s",(match) ? "YES":"NO");

另外一个谓词:

1
2
3
4
5
6
7
8
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"engine.horsepower >150"];
/*查看车库中哪些车的功率最大,可以循环测试每个汽车的谓词*/
NSArray *cars = [garage cars];
for(Car *car in cars){
if([predicate evaluateWithObject:car]){
NSLog(@"%@", car.name);
}
}

过滤器

如果我们不必像上文那样编写for循环和if语句,这有什么不好?实际上,某些类别将谓词过滤方法添加到了Cocoa集合类中。-filteredArrayUsingPredicate:是NSArray数组中的一种类别方法,它将循环过滤数组内容,根据谓词计算每个对象的值,并将值为YES的对象累积到将被返回的新数组中:

1
2
3
NSArray *results;
results = [cars filteredArrayUsingPredicate:predicate];
NSLog(@"%@",results);

假如有一个可变数组,你需要剔除不属于该数组的所有项目:

1
2
NSMutableArray *carsCopy = [cars mutableCopy];
[carsCopyfilterUsingPredicate:predicate];

格式说明符

资深编程人员都知道,硬编码并非好方法,因此,我们可以通过格式符构建谓词:

1
NSPredicate  *predicate = [NSPredicate predicateWithFormat:@"engine.horsepower > %d", 50];

运算符

NSPredicate的格式字符串包含大量不同的运算符。

1)比较和逻辑运算符

谓词字符串语法支持C语言中一些常用的运算符,例如等号运算符==和=。
不等号运算符具有各种形式:

1
2
3
4
5
>:大于
>=和=>:大于或等于
<:小于
<=和=<:小于或等于
!=和<>:不等于

此外,谓词字符串语法还支持括号表达式和AND、OR、NOT逻辑运算符或者C样式的等效表达式&&、||和!。

2)数组运算符

谓词字符串“(engine.horsepower> 50) OR (engine.horsepower < 200)”是一种十分常见的模式。等效于:

1
predicate= [NSPredicate predicateWithFormat:@"engine.horespower BETWEEN {50,200}"];

花括号表示数组,BETWEEN将数组中第一个元素看成是数组的下界,第二个元素看成是数组的上界。

1
2
NSArray *betweens = [NSArray arrayWithObjects:[NSNumber numberWithInt:50],[NSNumber   numberWithInt:200], nil];
predicate = [NSPredicate predicateWithFormat:@"engine.horsepower BETWEEN %@",betweens];

数组不仅仅用来指定某个区间的端点值。你可以使用IN运算符查找数组中是否含有某个特定值。

1
predicate = [NSPredicate predicateWithFormat:@"name IN {'Herbie', 'Snugs', 'Badger','Flap'}"];