jjzjj

iOS开发 NSPredicate的使用方法

BrianWang 2023-09-23 原文

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构造 之 格式字符串

有关iOS开发 NSPredicate的使用方法的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  4. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  5. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  6. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  7. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  8. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  9. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  10. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

随机推荐