Objective-C基础教程学习笔记,十七NSPredicate

Cocoa提供了一个名为NSPredicate的类,它用于指定过滤器的条件。可以创建NSPredicate对象,通过该对象准确地描述所需的条件,对每个对象通过谓词进行筛选,判断它们是否与条件相匹配。

这里的“谓词”通常用在数学和计算机科学概念中,表示计算真值或假值的函数。

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

创建谓词

在将NSPredicate应用于某个对象之前,首先需要创建它。可以通过两种基本方式来实现。第一种是创建许多对象,并将它们组合起来。这需要使用大量代码,如果正在构建通用用户接口来指定查询,采用这种方式比较简单。另一种方式是查询代码中的字符串。对初学者来说,这种方式比较简单。因此,本章重点介绍查询字符串。常见的面向字符串的API警告信息适用于查询字符串,特别适用于缺少编译器错误检查及有时出现奇怪的运行时错误等情况。

Car *car = makeCare(@”Herbie”,@”Honda”,@”CRX”,1984,2,110000,58);

[garage addCare:car];

构造了一辆汽车,品牌是Herbie,型号为双门1984 Honda CRX,马力引擎为58,已行驶距离为110000英里。

现在创建谓词:

NSPredicate *predicate;

predicate = [NSPredicate predicateWithFormat:@”name==‘Herbie’”];

我们将以上代码拆开分析。predicate是一个常用的Objective-C对象指针,它将指向NSPredicate对象。使用NSPredicate类方法+predicateWithFormat:来实际创建谓词。将某个字符串赋值给谓词,+predicateWithFormat:使用该字符串在后台构建对象树,这些树用来计算谓词的值。

这种谓词字符串看上去像是标准的C表达式。它的左侧是键路径name,随后是一个等于运算符“==”,右侧是一个引用字符串。如果谓词字符串中的文本块未被引用,则该谓词字符串被看做是键路径;如果引用了文本块,则认为它是文本字符串。你可以使用单引号或者双引号。通常应该使用单引号;否则,必须在字符串中对每个双引号进行转义。

计算谓词

我们将通过某个对象计算谓词

BOOL match = [predicate evaluateWithObject:car];

NSLog(@”%s”,match ? “YES”:”NO”);

-evaluateWithObject:通知接收对象(谓词)根据指定的对象计算自身的值。在本例中,接收对象为car,使用name作为键路径,应用valueForKeyPath:方法获取名称。然后,它将自身的值(即名称)与“Herbie”相比较。如果名称和“Herbie”相同,则-evaluateWithObject:返回YES,否则返回NO。此处,NSLog使用三元运算符将数值BOOL转换成人们可读的字符串形式。

以下是另一个谓词:

predicate = [NSPredicate predicateWithFormat:@”engine.horsepower > 150”];

match = [predicate evlueateWithObject:car];

此谓词字符串左侧是一个键路径。该键路径链接到汽车内部,查找引擎,然后查询引擎的马力。接下来,它将马力值与150进行比较。

通过特定的谓词条件检查单个对象时进展都很顺利,如果需要检查对象集合,情况就会变得更加有趣。假设我们需要查看车库中哪些汽车的功率最大,可以循环测试每个汽车的谓词:

NSArray *cars = [garage cars];

for(Car car in cars){

if([predicate evaluateWithObject:car]){

NSLog(@“%@”,car.name);

}

}

燃料过滤器

如果我们不必编写for循环和if语句,这有什么不好吗?程序中仅有几个代码行,但不包含代码就更好了。幸运的是,某些类别将谓词过滤方法添加到了Cocoa集合类中。

-filteredArrayUsingPredicate:是NSArray数组中的一种类别方法,它将循环过滤数组内容,根据谓词计算每个对象的值,并将值为YES的对象累积到将被返回的新数组中:

NSArray *results;

results = [cars filteredArrayUsingPredicate:predicate];

以上这些结果同前面的结果不一样。这里是一组汽车;前面的示例中,我们得到的结果是名称。我们可以使用KVC提取其中的名称。

假设有一个可变数组,你需要剔除不属于该数组的所有项目。NSMutableArray具有-filterUsingPredicate方法,它能轻松实现你的目标:

NSMutableArray *carsCopy = [cars mutableCopy];

[carCopy filterUsingPredicate:predicate];

也可以使用-filteredArrayUsingPredicate:。

我们在讨论KVC时提到过,使用谓词确实很便捷,但它的运行速度不会比你自己编写全部代码快。因为它无法避免在所有汽车之间使用循环和对每辆汽车进行某些操作。一般来说,这种循环并不会带来很大的性能影响,因为当今的计算机运行速度非常快。

格式说明符

可以通过两种方式将不同的内容放入谓词格式字符串中:格式说明符和变量名。首先,我们将介绍格式说明符。可以在你熟知的%d和%f格式说明符中使用数字形式的值:

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

当然,我们一般不直接在代码中使用值50,可以通过用户接口或某些扩展机制来驱动它。

队了使用printf说明符,也可以使用%@插入字符串值,将%@看成是一个引用字符串:

predicate = [NSPredicate predicateWithFormat:@”name==%@”,@”Herbie”];

注意,这里的格式字符串中没有引用%@。如果你需要引用%@,例如“name==’%@’”,应该将字符%和@放在谓词字符串中。

通过NSPredicate字符串,也可以使用%K指定键路径。该谓词和其它谓词相同,使用name==‘Herbie’作为条件:

predicate = [NSPredicate predicateWithFormat:@”%K == %@”,@”name”,@”Herbie”];

为了构造灵活的谓词,一种方式是使用格式说明符,另一种方式是将变量名放入字符串中,类似于环境变量:

NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:@”name==$NAME”];

现在,我们有一个含有变量的谓词。接下来,可以使用predicateWithSubstitutionVariables调用来构造新的专用谓词。创建一个键/值对字典,其中,键是变量名,值是插入谓词的内容:

NSDictionary *varDict;

varDict = [NSDictionary dictionaryWithObjectsAndKeys:@”Herbie”,@”NAME”,nil];

这里使用字符串”Herbie”作为键“NAME”的值。因此,构造以下形式的新谓词:

predicate = [predicateTemplate predicateWithSubstitutionVariables:varDict];

该谓词的工作方式和你所见过的其它谓词完全相同。

谓词运算符

>大于

>=和=>大于或等于

<小于

<=和=<小于或等于

!=和<>不等于

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

谓词字符串中的运算符不区分大小写。

不等号即适用于数字值又适用于字符串值。

数组运算符

predicate = [NSPredicate predicateWithFormat:@”engine.horsepower BETWEEN {50,200}”];

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

IN

predicate = [NSPredicate predicateWithFormat:@”name IN {‘Herbia’,‘Snugs’,‘Flap’}”];

SELF

SELF 可以引用用于谓词计算的对象。我们可以将谓词中所有的键路径表示成对应的SELF。此谓词和前面的谓词完全相同:

[predicate = NSPredicate predicateWithFormat:@”SELF.name in {‘Herbia’,‘Snugs’,‘Flap’}”];

字符串运算符

BEGINSWITH 检查某个字符串是否以另一个字符串开头。

ENDSWITH 检查某个字符串是否以另一个字符串结尾。

CONTAINS 检查某个字符串是否在另一个字符串内部。

例,使用“name BEGINWITH ‘Bad’”匹配”Badger”,使用”name ENDSWITH ‘vis’”匹配“Elvis”,使用”name CONTAINS udg”匹配“Judge”。

字符串匹配区分大小写。

为了减少名称匹配规则,可以为这些运算符添加[c],[d],或[cd]修饰符。其中,c表示不区分大小写,d表示不区分发音符号,[cd]表示“即不区分大小写,也不区分发音符号”。

LIKE运算符

谓词字符串“name LIKE ‘*er*’”将会与任何含有”er”的名称相匹配。这等效于CONTAINS。谓词字符串“name LIKE ‘???er*’”将会匹与Paper Car 相匹配,一个?代表一个字符。