iOS开发 NSPredicate的使用方法

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 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:

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


Acknowledgements:

  1. iOS开发-最优办法查找两个数组相同与不同的数据
  2. iOS开发之NSPredicate谓词的用法
  3. iOS中的谓词(NSPredicate)使用
  4. iOS开发之NSPredicate
  5. iOS 探讨之 NSPredicate构造 之 格式字符串
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 一、快捷键 ctr+b 执行ctr+/ 单行注释ctr+c ...
    o_8319阅读 5,779评论 2 16
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,567评论 18 399
  • 「虚构」 作者:望云 80年代出生,坐望山特约作者。自由写作者。 1 这个故事是用第一人称写的,在它有幸被你看到之...
    坐望山阅读 173评论 0 0
  • 你有没有遇到过这样的一个人, 在每天晴朗的早晨,在每天夕阳西下的傍晚,在拥挤的人群中...... 你一定遇见过这样...
    周拾七阅读 242评论 0 2
  • 每句晚安都是我爱你 爱情有时是一场后青春期的冲动 它让我们直面自己的疯狂 超越时间和距离 又一个睡前暖心小故事。开...
    筱淼渺阅读 275评论 0 0