iOS开发 NSPredicate的使用方法

呼延衡
2023-12-01

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];
通过predicatepredicateFormat属性可输出谓词的格式字符串为: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 Hadlinks on 2018/9/12.
//  Copyright © 2018 Hadlinks. 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 Hadlinks on 2018/9/12.
//  Copyright © 2018 Hadlinks. 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
  • 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"
  • 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
  • name LIKE "Lady Sybil*"
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
  • name LIKE "?????S*"
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);

使用谓词过滤不可变集合和可变集合的区别是:过滤不可变集合时,会返回符合条件的集合元素组成的新集合;过滤可变集合时,没有返回值,会直接剔除不符合条件的集合元素。

Acknowledgements:

  1. iOS开发-最优办法查找两个数组相同与不同的数据
  2. iOS开发之NSPredicate谓词的用法
  3. iOS中的谓词(NSPredicate)使用
  4. iOS开发之NSPredicate
  5. iOS 探讨之 NSPredicate构造 之 格式字符串
 类似资料: