NSPredicate的常见使用

转自NSPredicate的使用
版本:iOS13.7

一、简介

给NSPredicate(谓词)设定一个表达式,可评估对象是否符合该表达式,也可过滤出符合表达式的数据(NSArray、NSSet、NSOrderedSet)。
NSPredicate有两个子类,分别是NSComparisonPredicate和NSCompoundPredicate。
NSComparisonPredicate 比较谓词,点击NSComparisonPredicate的使用了解。
NSCompoundPredicate 复合谓词,点击NSCompoundPredicate的使用了解。

二、NSPredicate的API

//通过表达式创建一个谓词实例
//关于表达式 详见(四、谓语表达式语法)
+ (NSPredicate *)predicateWithFormat:(NSString *)predicateFormat, ...;
//通过表达式创建一个谓词实例
//表达式中的%@占位符将从arguments中取值,占位符数量必须小于等于arguments元素的数量
//详见说明1
+ (NSPredicate *)predicateWithFormat:(NSString *)predicateFormat argumentArray:(nullable NSArray *)arguments;
//通过表达式创建一个谓词实例
//表达式中的%@占位符将从argList中取值,占位符数量必须小于等于argList元素的数量
//详见说明1
+ (NSPredicate *)predicateWithFormat:(NSString *)predicateFormat arguments:(va_list)argList;

//该方法只有macos可用,暂不说明
+ (nullable NSPredicate *)predicateFromMetadataQueryString:(NSString *)queryString;
API_AVAILABLE(macos(10.9)) API_UNAVAILABLE(ios, watchos, tvos)

//创建一个结果始终为YES/NO的谓词实例
//详见说明2
+ (NSPredicate *)predicateWithValue:(BOOL)value; 

//根据回调的返回值来创建一个结果为YES/NO谓词实例
//其中回调的evaluatedObject和bindings分别为evaluateWithObject方法的object和bindings
//详见例1
+ (NSPredicate*)predicateWithBlock:(BOOL (^)(id _Nullable evaluatedObject, NSDictionary<NSString *, id> * _Nullable bindings))block;

//返回谓词的表达式字符串 只读
//[NSPredicate predicateWithFormat:@"SELF CONTAINS 'world'"]的表达式为SELF CONTAINS "world"
@property (readonly, copy) NSString *predicateFormat; 

//用常量值代替变量
//即用字典中的键值对替换用$声明的变量
//详见说明3
- (instancetype)predicateWithSubstitutionVariables:(NSDictionary<NSString *, id> *)variables;  

//评估对象是否符合该谓词
- (BOOL)evaluateWithObject:(nullable id)object;

//评估对象是否符合该谓词
//其中bindings字典中的元素可以代替用$声明的变量,详见predicateWithSubstitutionVariables方法的说明3
//也可以作为回调的参数,详见predicateWithBlock方法的例1
- (BOOL)evaluateWithObject:(nullable id)object substitutionVariables:(nullable NSDictionary<NSString *, id> *)bindings;

//强制使用已安全解码的谓词以进行评估
//当使用 NSSecureCoding编码的NSPredicate对象在安全解码时,是不能被评估的。
//因为评估一个从存档中取出的predicate是有潜在风险的。
//在激活评估之前,我们应该证实key paths, selectors,和其他细节来确保没有错误或恶意代码被执行。
//一旦我们验证了predicate,我们就可以通过调用allowEvaluation来激活接收器来评估。
- (void)allowEvaluation;

说明1:以下三种初始化方法的意义相同,最后的生成的表达式都为SELF CONTAINS "world"

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS 'world'"];
等价于
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS %@", @"world"];
等价于
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS %@"
                         argumentArray:@[@"world"]];
//若要使用双引号" ",需要加上转义符\
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS \"world\""];

说明2:当调用evaluateWithObject时,无论传入的对象是什么,只会返回YES/NO,以下

NSPredicate *predicate = [NSPredicate predicateWithValue:YES];
等价于
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"TRUEPREDICATE"];

NSPredicate *predicate = [NSPredicate predicateWithValue:NO];
等价于
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"FALSEPREDICATE"];

说明3:输出的格式字符串为SELF CONTAINS "wor"
实际上和NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS %@", @"wor"];实现的效果相同

   NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS $variable"];
   //字典中的key需要和$后面声明的变量相同,可以声明多个变量,但字典中需要全部包含
   predicate = [predicate predicateWithSubstitutionVariables:@{@"variable":@"wor"}];
   NSLog(@"%@", [predicate predicateFormat]);

 //举例
   NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(NSArray * _Nullable >evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
       //通过回调的evaluatedObject和bindings来判断是否过滤
       if ([evaluatedObject containsObject:[bindings objectForKey:@"key"]]) {
           return YES;
       } else {
           return NO;
       }
   }];
   BOOL eva = [predicate evaluateWithObject:@[@"1", @"2"] >substitutionVariables:@{@"key":@"2"}];
   NSLog(@"%@", @(eva));
输出:
1

三、NSPredicate的扩展API

@interface NSArray<ObjectType> (NSPredicateSupport)

//将数组中的每个元素进行评估,返回所有符合谓词的元素的新数组
- (NSArray<ObjectType> *)filteredArrayUsingPredicate:(NSPredicate *)predicate; 

@end
@interface NSMutableArray<ObjectType> (NSPredicateSupport)

//将数组中的每个元素进行评估,移除所有不符合谓词的元素
- (void)filterUsingPredicate:(NSPredicate *)predicate; 

@end

filteredArrayUsingPredicate会返回一个新数组,原数组不会变化。
filterUsingPredicate会直接改变原数组的内容,如有需要,请先对原数组备份。
例:

    //选出大于10且小于50的元素
    predicate = [NSPredicate predicateWithFormat:@"SELF > 10 AND SELF < 50"];
    NSArray *array = @[@20, @30, @40, @50, @10, @5];
    NSMutableArray *muta = [array mutableCopy];
    NSLog(@"过滤前\narray = %@\n muta = %@", array, muta);
    NSArray *array1 = [array filteredArrayUsingPredicate:predicate];
    [muta filterUsingPredicate:predicate];
    NSLog(@"过滤后\narray = %@\n array1 = %@\n muta = %@", array, array1, muta);
输出:
过滤前
array = (
    20,
    30,
    40,
    50,
    10,
    5
)
 muta = (
    20,
    30,
    40,
    50,
    10,
    5
)
过滤后
array = (
    20,
    30,
    40,
    50,
    10,
    5
)
 array1 = (
    20,
    30,
    40
)
 muta = (
    20,
    30,
    40
)

NSSet、NSMutableSet、NSOrderedSet、NSMutableOrderedSet的NSPredicate扩展API
功能与NSArray、NSMutableArray的扩展API相同

@interface NSSet<ObjectType> (NSPredicateSupport)
- (NSSet<ObjectType> *)filteredSetUsingPredicate:(NSPredicate *)predicate ;
@end

@interface NSMutableSet<ObjectType> (NSPredicateSupport)
- (void)filterUsingPredicate:(NSPredicate *)predicate ;
@end

@interface NSOrderedSet<ObjectType> (NSPredicateSupport)
- (NSOrderedSet<ObjectType> *)filteredOrderedSetUsingPredicate:(NSPredicate *)p;
@end

@interface NSMutableOrderedSet<ObjectType> (NSPredicateSupport)
- (void)filterUsingPredicate:(NSPredicate *)p;
@end

四、谓语表达式语法:表达式字符串并不会进行语义检测,必须保证正确性

表达式语法参考简要理解 - NSPredicate

占位符
$:声明变量,例如 $variable,详见说明3
%K :属性名
%@ :属性值

   NSString *name = @"key1";
   NSString *value = @"23";
   //字典中的键为key1的值是否包含"23"
   predicate = [NSPredicate predicateWithFormat:@"key1 CONTAINS %@", value];
   NSLog(@"%@", [predicate predicateFormat]);
   predicate = [NSPredicate predicateWithFormat:@"%K CONTAINS %@", name, value];
   NSLog(@"%@", [predicate predicateFormat]);
   eva = [predicate evaluateWithObject:@{@"key1":@"value12345"}];
   NSLog(@"%@", @(eva));
输出:
key1 CONTAINS "23"
key1 CONTAINS "23"
1

可以看到两种初始化的表达式字符串都是key1 CONTAINS "23"
%@表示的是"23"这个带双引号的值,%K表示的是不带引号key1
%K好像不能表示关键字,如SELF等

基本比较运算
=、== 左边是否等于右边
=、 => 左边是否大于或等于右边
<=、=< 左边是否小于或等于右边
左边是否大于右边
< 左边是否小于右边
!=、<> 左边是否不等于右边
BETWEEN 左边的值是否在区间内(包含边界数值)

例如@"SELF BETWEEN {10, 50}"等价于@"SELF >= 10 AND SELF <= 50"

布尔值谓语
TRUEPREDICATE 始终返回真
FALSEPREDICATE 始终返回假

例如[NSPredicate predicateWithValue:YES]的表达式实际上就是TRUEPREDICATE

基本复合谓语
AND、&& 与
OR、|| 或
NOT、!: 非

例如[NSPredicate predicateWithFormat:@"SELF > 10 AND SELF < 50"]表示大于10并且小于50
[NSPredicate predicateWithFormat:@"SELF > 10 or SELF < 5"]表示大于10或者小于5
[NSPredicate predicateWithFormat:@"NOT SELF > 10"]表示不大于10

字符串比较
BEGINSWITH 左边字符串是否以右边字符串开始
ENDSWITH 左边字符串是否以右边字符串结束
CONTAINS 左边字符串内容是否包含右边字符串
LIKE 左边字符串是否等于右边字符串
MATCHES 左边字符串是否符合右边的正则表达式

例如[NSPredicate predicateWithFormat:@"SELF BEGINSWITH 'world'"]表示worldxxx这种字符串,xxx表示0个或多个字符

[NSPredicate predicateWithFormat:@"SELF ENDSWITH 'world'"]表示xxxworld这种字符串
[NSPredicate predicateWithFormat:@"SELF CONTAINS 'world'"]表示xxxworldxxx这种字符串
[NSPredicate predicateWithFormat:@"SELF LIKE 'world'"]表示字符串world
[NSPredicate predicateWithFormat:@"SELF MATCHES '^1[3|4|5|7|8][0-9]{9}$'"]表示符合手机号正则表达式的字符串

可以在关键字后面加在[cd],c表示忽略大小写,即CAFE与cafe为相同字符串;d表示忽略读音,即café与cafe为相同字符串。
例如[NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] 'CAfé'"]与[NSPredicate predicateWithFormat:@"SELF CONTAINS 'cafe'"]功能相同

LIKE后面可以使用通配符?和,但不能在%@占位符的值中使用
?表示指代一个字符 表示指代0个或多个字符
例如[NSPredicate predicateWithFormat:@"SELF LIKE '?world
'"]表示aworldaaa这种字符串
[NSPredicate predicateWithFormat:@"SELF LIKE %@", @"?world
"],这种是错误写法,只能识别字符串?world,?在此处不能当成通配符

集合操作
ANY、SOME 是否对象的元素至少有一个满足后面的表达式
ALL 是否对象的元素全都满足后面的表达式
NONE 是否对象的元素全都不满足后面的表达式
上面的三种,评估的对象(evaluateWithObject的参数)必须是NSArray或NSSet

例如[NSPredicate predicateWithFormat:@"ANY SELF CONTAINS[cd] 'cafe'"]
表示@[@"cafe1", @"c afe2", @"c afe3"]这种数组
[NSPredicate predicateWithFormat:@"ALL SELF CONTAINS[cd] 'cafe'"]
表示@[@"cafe1", @"cafe2", @"cafe3"]这种数组
[NSPredicate predicateWithFormat:@"NONE SELF CONTAINS[cd] 'cafe'"]
表示@[@"c afe1", @"c afe2", @"c afe3"]这种数组

IN 左边元素是否在右边元素集合出现,集合可以是NSArray、NSSet、NSDictionary,如果是NSDictionary,将取其所有value的值的数组。

例:
  //cafe是否在集合中
   predicate = [NSPredicate predicateWithFormat:@"SELF IN[cd] {'cafe', 'apple', 'tea'}"];
  eva = [predicate evaluateWithObject:@"cafe"];
   NSLog(@"%@", @(eva));
输出:1

当evaluateWithObject的参数为NSArray、NSSet、NSDictionary时,可使用以下写法表示单个元素。
SELF[index],表示取对应索引的元素,index不要越界
SELF[FIRST],表示第一个元素
SELF[LAST],表示最后一个元素
SELF[SIZE],表示元素的个数

   //数组的第0个元素是否在集合中
   predicate = [NSPredicate predicateWithFormat:@"SELF[0] IN[cd] {'cafe', 'apple', 'tea'}"];
   eva = [predicate evaluateWithObject:@[@"cafe", @"cafe1"]];
   NSLog(@"%@", @(eva));
输出:1

若为NSDictionary,可使用
SELF['key']或SELF.key或key(即不要前面的SELF),表示对应key的元素

   //字典key为key1的值是否在集合中
   predicate = [NSPredicate predicateWithFormat:@"SELF.key1 IN[cd] {'cafe', 'apple', 'tea'}"];
   eva = [predicate evaluateWithObject:@{@"key1":@"apple"}];
   NSLog(@"%@", @(eva));
输出:1

字面量语义
FALSE、NO 逻辑假
TRUE、YES 逻辑真

   predicate = [NSPredicate predicateWithFormat:@"SELF = TRUE"];
   eva = [predicate evaluateWithObject:@(YES)];

五、应用

//PredicateModel的.h文件
@interface PredicateModel : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *adress;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) PredicateModel *subModel;

@end
//PredicateModel的.m文件
@implementation PredicateModel

- (NSString *)description {
    return [NSString stringWithFormat:@"name = %@ adress = %@ age = %ld subModel = (%@)",
           self.name, self.adress, self.age, self.subModel?:@"empty"];
}

@end
    NSArray *names = @[@"aa", @"bb", @"cc"];
    NSMutableArray *models = [NSMutableArray array];
    for (NSInteger i = 0; i < 6; i++) {
        PredicateModel *model = [[PredicateModel alloc] init];
        model.name = [NSString stringWithFormat:@"%@", names[i%3]];
        model.adress = [NSString stringWithFormat:@"adress%ld", i];
        model.age = 10+random()%5;
        if (i%2 == 1) {
            //1 3 5时将上个model添加成subModel
            model.subModel = [models lastObject];
        }
        [models addObject:model];
    }
    NSLog(@"models = %@", models);
    //查询age>12且name为aa或cc的model
    NSPredicate *modelPredicate = [NSPredicate predicateWithFormat:@"SELF.age > 12 AND (name = %@ OR name = %@)", names.firstObject, names.lastObject];
    NSArray *result = [models filteredArrayUsingPredicate:modelPredicate];
    NSLog(@"result = %@", result);
    //二级查询 查询subModel存在并且subModel的age>12的model
    modelPredicate = [NSPredicate predicateWithFormat:@"subModel != NULL AND subModel.age >12"];
    result = [models filteredArrayUsingPredicate:modelPredicate];
    NSLog(@"result = %@", result);
输出:
models = (
    "name = aa adress = adress0 age = 13 subModel = (empty)",
    "name = bb adress = adress1 age = 11 subModel = (name = aa adress = adress0 age = 13 subModel = (empty))",
    "name = cc adress = adress2 age = 12 subModel = (empty)",
    "name = aa adress = adress3 age = 10 subModel = (name = cc adress = adress2 age = 12 subModel = (empty))",
    "name = bb adress = adress4 age = 13 subModel = (empty)",
    "name = cc adress = adress5 age = 10 subModel = (name = bb adress = adress4 age = 13 subModel = (empty))"
)
result = (
    "name = aa adress = adress0 age = 13 subModel = (empty)"
)
result = (
    "name = bb adress = adress1 age = 11 subModel = (name = aa adress = adress0 age = 13 subModel = (empty))",
    "name = cc adress = adress5 age = 10 subModel = (name = bb adress = adress4 age = 13 subModel = (empty))"
)

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

推荐阅读更多精彩内容