1. 概念
1. 谓词(NSPredicate)是什么?
Xcode的开发者文档中的解释:
A definition of logical conditions used to constrain a search either for a fetch or for in-memory filtering.
🤓我的翻译:
NSPredicate
是一个逻辑条件的定义,这个逻辑条件用来约束一个搜索条件,而这个搜索条件用于数据的获取或内存中数据的过滤。
它其实就是一个过滤器。
在Cocoa
中,NSPredicate
是一个可以根据对象的性质或者相互关系来进行逻辑判断的工具。
2. 谓词怎么创建?
创建谓词有三种方式:
- 用格式字符串创建;
- 用指定函数创建;
- 用谓词模板创建;
这里主要采用格式字符串的方式来创建谓词。(🤫我也只了解了这一部分)
2.1 示例:创建谓词
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K > %d", @"score", 90];
通过predicate
的predicateFormat
属性可输出谓词的格式字符串为:score > 90
,即分数大于90分这个过滤条件。
2.2 谓词字符串解析器
采用格式字符串方式来创建谓词时,就需要通过解析器来解析格式字符串,然后转换成相应的逻辑条件。
- 解析器对空格、关键字的大小写是不敏感的,并支持嵌套的括号表达式;
- 解析器不执行语义类型的检查;
-
%@
:用于参数替换,目标为对象类型,如:NSString、NSNumber、NSDate
等; -
%K
:用于参数替换,目标为键值;
⚠️ 用单引号'
、双引号""
包围的%@
和%K
或者是包围的$
变量名都会被直接转换为字面的意思。
[NSPredicate predicateWithFormat:@"%K Like %@", @"name", @"Zhangsan"]
谓词格式字符串为: name LIKE "Zhangsan"
[NSPredicate predicateWithFormat:@"%K Like '%@'", @"name", @"Zhangsan"]
[NSPredicate predicateWithFormat:@"%K Like \"%@\"", @"name", @"Zhangsan"]
谓词格式字符串为: name LIKE "%@"
[NSPredicate predicateWithFormat:@"'%K' Like %@", @"name", @"Zhangsan"]
[NSPredicate predicateWithFormat:@"\"%K\" Like %@", @"name", @"Zhangsan"]
谓词格式字符串为: "%K" LIKE "name"
2. 使用
2.1 前期准备
创建Student
类,并初始化三个Student
对象,然后添加到数组中。
Student类:
Student.h
//
// Student.h
// NSPredicateDemo
//
// Created by wz on 2018/9/12.
// Copyright © 2018 BTStudio. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Student : NSObject
/** 姓名 */
@property (nonatomic, copy) NSString *name;
/** 分数 */
@property (nonatomic, assign) int score;
/** 分数2 */
@property (nonatomic, strong) NSNumber *score2;
/** 等级 */
@property (nonatomic, assign) int grade;
@end
Student.m
//
// Student.m
// NSPredicateDemo
//
// Created by wz on 2018/9/12.
// Copyright © 2018 BTStudio. All rights reserved.
//
#import "Student.h"
@implementation Student
@end
初始化:
Student *student0 = [[Student alloc] init];
student0.name = @"Lady Mary Crawley";
student0.score = 70;
student0.grade = 6;
Student *student1 = [[Student alloc] init];
student1.name = @"Lady Edith Crawley";
student1.score = 90;
student1.grade = 7;
Student *student2 = [[Student alloc] init];
student2.name = @"Lady Sybil Crawley";
student2.score = 98;
student2.grade = 7;
NSArray *students = @[student0, student1, student2];
2.2 使用示例
以下示例主要介绍了使用 谓词 来过滤数组中元素这一功能。
2.2.1 比较运算符
>
大于
<
小于
>=
大于等于
<=
小于等于
=
或==
等于
!=
或<>
不等于
大于:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K > %d", @"score", 90];
NSLog(@"predicate.predicateFormat: %@", predicate.predicateFormat);
NSArray *filterStudents = [students filteredArrayUsingPredicate:predicate];
[filterStudents enumerateObjectsUsingBlock:^(Student *student, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"[score > 90] student name is: %@", student.name);
}];
输出为:
predicate.predicateFormat: score > 90
[score > 90] student name is: Lady Sybil Crawley
不等于:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K != %d", @"score", 90];
NSLog(@"predicate.predicateFormat: %@", predicate.predicateFormat);
NSArray *filterStudents = [students filteredArrayUsingPredicate:predicate];
[filterStudents enumerateObjectsUsingBlock:^(Student *student, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"[score != 90] student name is: %@", student.name);
}];
输出为:
predicate.predicateFormat: score != 90
[score != 90] student name is: Lady Mary Crawley
[score != 90] student name is: Lady Sybil Crawley
2.2.2 逻辑运算符
AND
或 &&
: 与
OR
或 ||
: 或
NOT
或 !
: 非
与:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K != %d && %K = %d", @"score", 90, @"grade", 7];
NSLog(@"predicate.predicateFormat: %@", predicate.predicateFormat);
NSArray *filterStudents = [students filteredArrayUsingPredicate:predicate];
[filterStudents enumerateObjectsUsingBlock:^(Student *student, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"[score != 90 且 grade == 7] student name is: %@", student.name);
}];
输出为:
predicate.predicateFormat: score != 90 AND grade == 7
[score != 90 且 grade == 7] student name is: Lady Sybil Crawley
2.2.3 关系运算符
NONE
没有元素,等同于NOT ANY
(集合中没有任何元素满足条件就返回YES。如:NONE person.age < 18
,表示person集合中所有元素的age>=18
时,才返回YES)
ANY
任意一个 (集合中任意一个元素满足条件,就返回YES)
SOME
一些,等同于ANY
(集合中任意一个元素满足条件,就返回YES)
ALL
所有元素 (集合中所有元素都满足条件,才返回YES)
IN
包含 (等价于SQL语句中的IN
运算符,只有当左边表达式或值出现在右边的集合中才会返回YES)
BETWEEN
范围,例如:BETWEEN {10, 20}
,表示大于等于10,小于等于20的范围
包含 IN
:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K IN {%d, %d, %d, %d, %d}", @"score", 70, 75, 80, 85, 90];
NSLog(@"predicate.predicateFormat: %@", predicate.predicateFormat);
NSArray *filterStudents = [students filteredArrayUsingPredicate:predicate];
[filterStudents enumerateObjectsUsingBlock:^(Student *student, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"分数在{70, 75, 80, 85, 90}之中的学生姓名是: %@", student.name);
}];
输出为:
predicate.predicateFormat: score IN {70, 75, 80, 85, 90}
分数在{70, 75, 80, 85, 90}之中的学生姓名是: Lady Mary Crawley
分数在{70, 75, 80, 85, 90}之中的学生姓名是: Lady Edith Crawley
查找两个数组中相同或不同的元素
Student *student0 = [[Student alloc] init];
student0.name = @"Lady Mary Crawley";
Student *student1 = [[Student alloc] init];
student1.name = @"Lady Edith Crawley";
Student *student2 = [[Student alloc] init];
student2.name = @"Lady Sybil Crawley";
Student *student3 = [[Student alloc] init];
student3.name = @"Thomas·小火车";
NSArray *arr1 = @[student0, student1];
NSArray *arr2 = @[student1, student2, student3];
// 1. 查找相同的元素
NSPredicate *filterPredicateSame = [NSPredicate predicateWithFormat:@"SELF IN %@", arr2];
NSArray *sameArr = [arr1 filteredArrayUsingPredicate:filterPredicateSame];
[sameArr enumerateObjectsUsingBlock:^(Student *student, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"两个数组中相同的元素有 = %@", student.name);
}];
// 2. 查找不同的元素
NSPredicate *filterPredicateDiff = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", arr2];
// arr1中不同的元素
NSArray *diffArr1 = [arr1 filteredArrayUsingPredicate:filterPredicateDiff];
// arr2中不同的元素
filterPredicateDiff = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", arr1];
NSArray *diffArr2 = [arr2 filteredArrayUsingPredicate:filterPredicateDiff];
NSMutableArray *diffArr = [NSMutableArray arrayWithArray:diffArr1];
[diffArr addObjectsFromArray:diffArr2];
[diffArr enumerateObjectsUsingBlock:^(Student *student, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"两个数组中不同的元素有 = %@", student.name);
}];
输出为:
两个数组中相同的元素有 = Lady Edith Crawley
数组中不同的元素有 = Lady Mary Crawley
数组中不同的元素有 = Lady Sybil Crawley
数组中不同的元素有 = Thomas·小火车
范围之间 BETWEEN
:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K BETWEEN {%d, %d}", @"score", 90, 100];
NSLog(@"predicate.predicateFormat: %@", predicate.predicateFormat);
NSArray *filterStudents = [students filteredArrayUsingPredicate:predicate];
[filterStudents enumerateObjectsUsingBlock:^(Student *student, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"分数在{90, 100}之间的学生姓名是: %@", student.name);
}];
输出为:
predicate.predicateFormat: score BETWEEN {90, 100}
分数在{90, 100}之间的学生姓名是: Lady Edith Crawley
分数在{90, 100}之间的学生姓名是: Lady Sybil Crawley
NONE
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NONE %K < %d", @"score", 60];
NSLog(@"predicate.predicateFormat: %@", predicate.predicateFormat);
BOOL flunk = [predicate evaluateWithObject:students];
NSLog(@"%@", flunk ? @"三名学生都及格了" : @"有不及格的学生");
输出为:
predicate.predicateFormat: NOT ANY score < 60
三名学生都及格了
2.2.4 字符串相关
SELF
字符串本身(代表正在被判断的对象自身)
BEGINSWITH
以什么开头
ENDSWITH
以什么结尾
CONTAINS
包含
LIKE
匹配
*
通配符(配合LIKE
使用)
?
代表一个字符 (配合LIKE
使用)
MATCHES
正则表达式
- 包含
CONTAINS
:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K CONTAINS %@", @"name", @"S"];
NSLog(@"predicate.predicateFormat: %@", predicate.predicateFormat);
NSArray *filterStudents = [students filteredArrayUsingPredicate:predicate];
[filterStudents enumerateObjectsUsingBlock:^(Student *student, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"学生姓名中包含字母'S'的有: %@", student.name);
}];
输出为:
predicate.predicateFormat: name CONTAINS "S"
学生姓名中包含字母'S'的有: Lady Sybil Crawley
- 以什么开头
BEGINSWITH
、 以什么结尾ENDSWITH
:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K BEGINSWITH %@", @"name", @"Lady E"];
NSLog(@"predicate.predicateFormat: %@", predicate.predicateFormat);
NSArray *filterStudents = [students filteredArrayUsingPredicate:predicate];
[filterStudents enumerateObjectsUsingBlock:^(Student *student, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"学生姓名中\"Lady E\"开头的有: %@", student.name);
}];
输出为:
predicate.predicateFormat: name BEGINSWITH "Lady E"
学生姓名中"Lady E"开头的有: Lady Edith Crawley
- 匹配
LIKE
:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K LIKE %@", @"name", @"Mary"];
NSLog(@"predicate.predicateFormat: %@", predicate.predicateFormat);
NSArray *filterStudents = [students filteredArrayUsingPredicate:predicate];
[filterStudents enumerateObjectsUsingBlock:^(Student *student, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"学生姓名精确匹配'Mary'的有: %@", student.name);
}];
输出为:
predicate.predicateFormat: name LIKE "Mary"
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K LIKE %@", @"name", @"*Mary*"];
NSLog(@"predicate.predicateFormat: %@", predicate.predicateFormat);
NSArray *filterStudents = [students filteredArrayUsingPredicate:predicate];
[filterStudents enumerateObjectsUsingBlock:^(Student *student, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"学生姓名模糊匹配'Mary'的有: %@", student.name);
}];
输出为:
predicate.predicateFormat: name LIKE "*Mary*"
学生姓名模糊匹配'Mary'的有: Lady Mary Crawley
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K LIKE %@", @"name", @"Lady Sybil*"];
NSLog(@"predicate.predicateFormat: %@", predicate.predicateFormat);
NSArray *filterStudents = [students filteredArrayUsingPredicate:predicate];
[filterStudents enumerateObjectsUsingBlock:^(Student *student, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"学生姓名模糊匹配'Lady Sybil'的有: %@", student.name);
}];
输出为:
predicate.predicateFormat: name LIKE "Lady Sybil*"
学生姓名模糊匹配'Lady Sybil'的有: Lady Sybil Crawley
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K LIKE %@", @"name", @"?????S*"];
NSLog(@"predicate.predicateFormat: %@", predicate.predicateFormat);
NSArray *filterStudents = [students filteredArrayUsingPredicate:predicate];
[filterStudents enumerateObjectsUsingBlock:^(Student *student, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"学生姓名中第6个字符为'S'的有: %@", student.name);
}];
输出为:
predicate.predicateFormat: name LIKE "?????S*"
学生姓名中第6个字符为'S'的有: Lady Sybil Crawley
⚠️ 字符串比较都是区分大小写和重音符号的。如:café和cafe是不一样的,Cafe和cafe也是不一样的。如果希望字符串比较时不区分大小写和重音符号,请在这些运算符后使用[c]
、[d]
选项。其中[c]
是不区分大小写,[d]
是不区分重音符号,其写在字符串比较运算符之后。比如:name LIKE[cd] 'cafe'
,那么不论name是cafe、Cafe还是café,表达式都会返回YES。
3. 其他
3.1 保留字
下列单词都是保留字(不论大小写)
AND、OR、IN、NOT、ALL、ANY、SOME、NONE、LIKE、CASEINSENSITIVE、CI、MATCHES、CONTAINS、BEGINSWITH、ENDSWITH、BETWEEN、NULL、NIL、SELF、TRUE、YES、FALSE、NO、FIRST、LAST、SIZE、ANYKEY、SUBQUERY、CAST、TRUEPREDICATE、FALSEPREDICATE
注:虽然大小写都可以,但是更推荐使用大写来表示这些保留字。
3.2 直接量
在谓词表达式中可以使用如下直接量:
FALSE、NO
:代表逻辑假
TRUE、YES
:代表逻辑真
NULL、NIL
:代表空值
SELF
:代表正在被判断的对象自身
"string"或'string'
:代表字符串
数组:和C语言中的写法相同,如:{'one', 'two', 'three'}
数值:包括整数、小数和科学计数法表示的形式
十六进制数 :0x
开头的数字
八进制 :0o
开头的数字
二进制 :0b
开头的数字
3.3 $
变量名
属性作为key时,可以用%K
来表示,那么参数呢?
对于参数,则可以使用$
修饰的字符来表示,在predicateWithSubstitutionVariables
中使用字典的形式赋值,这种赋值方式方便产生多个条件类似的过滤器。其中VALUE
字符串也可以替换为其他字符串,只要前后统一即可,最好不要用关键字。
创建谓词,属性名为age
,使用%K
来表示,其参数使用$VALUE
来表示。
NSPredicate *predTemp = [NSPredicate predicateWithFormat:@"%K > $VALUE", @"age"];
// 指定 $VALUE 的值为 25
NSPredicate *pred1 = [predTemp predicateWithSubstitutionVariables:@{@"VALUE" : @25}];
NSArray *newArray1 = [array filteredArrayUsingPredicate:pred1];
NSLog(@"newArray1:%@", newArray1);
// 修改 $VALUE 的值为 32
NSPredicate *pred2 = [predTemp predicateWithSubstitutionVariables:@{@"VALUE" : @32}];
NSArray *newArray2 = [array filteredArrayUsingPredicate:pred2];
NSLog(@"newArray2:%@", newArray2);
PS:
- 使用谓词过滤不可变集合和可变集合的区别是:过滤不可变集合时,会返回符合条件的集合元素组成的新集合;过滤可变集合时,没有返回值,会直接剔除不符合条件的集合元素。