iOS中的謂詞(NSPredicate)使用

首先,我們需要知道何謂謂詞,讓我們看看官方的解釋:

The NSPredicate class is used to define logical conditions used to constrain a search either for a fetch or for in-memory filtering.

NSPredicate類是用來定義邏輯條件約束的獲取或內存中的過濾搜索。
可以使用謂詞來表示邏輯條件,用於描述對象持久性存儲在內存中的對象過濾。其實意思就是:我是一個過濾器,不符合條件的都滾開


一、NSPredicate的基本語法

我們使用一門語言,無論是外語還是計算機語言,總是從語法開始的,這樣我們才能正確的把握邏輯。所以我們從語法開始說起。在這部分我們僅關心其語法的使用
只要我們使用謂詞(NSPredicate)都需要爲謂詞定義謂詞表達式,而這個表達式必須是一個返回BOOL的值。
謂詞表達式由表達式、運算符和值構成。

1.比較運算符

比較運算符如下

  • =、==:判斷兩個表達式是否相等,在謂詞中=和==是相同的意思都是判斷,而沒有賦值這一說
    NSNumber *testNumber = @123;
      NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF = 123"];
      if ([predicate evaluateWithObject:testNumber]) {
          NSLog(@"testString:%@", testNumber);
      }
    我們可以看到輸出的內容爲:
    2016-01-07 11:12:27.281 PredicteDemo[4130:80412] testString:123
  • >=,=>:判斷左邊表達式的值是否大於或等於右邊表達式的值
  • <=,=<:判斷右邊表達式的值是否小於或等於右邊表達式的值
  • >:判斷左邊表達式的值是否大於右邊表達式的值
  • <:判斷左邊表達式的值是否小於右邊表達式的值
  • !=、<>:判斷兩個表達式是否不相等
  • BETWEEN:BETWEEN表達式必須滿足表達式 BETWEEN {下限,上限}的格式,要求該表達式必須大於或等於下限,並小於或等於上限
    NSNumber *testNumber = @123;
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF BETWEEN {100, 200}"];
      if ([predicate evaluateWithObject:testNumber]) {
          NSLog(@"testString:%@", testNumber);
      } else {
          NSLog(@"不符合條件");
      }
    輸出結果爲:
    2016-01-07 11:20:39.921 PredicteDemo[4366:85408] testString:123

    2.邏輯運算符

  • AND、&&:邏輯與,要求兩個表達式的值都爲YES時,結果才爲YES。
    NSArray *testArray = @[@1, @2, @3, @4, @5, @6];
      NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF > 2 && SELF < 5"];
      NSArray *filterArray = [testArray filteredArrayUsingPredicate:predicate];
      NSLog(@"filterArray:%@", filterArray);
    輸出結果爲:
    2016-01-07 11:27:01.885 PredicteDemo[4531:89537] filterArray:(
      3,
      4
    )
  • OR、||:邏輯或,要求其中一個表達式爲YES時,結果就是YES
  • NOT、 !:邏輯非,對原有的表達式取反

3.字符串比較運算符

  • BEGINSWITH:檢查某個字符串是否以指定的字符串開頭(如判斷字符串是否以a開頭:BEGINSWITH 'a')
  • ENDSWITH:檢查某個字符串是否以指定的字符串結尾
  • CONTAINS:檢查某個字符串是否包含指定的字符串
  • LIKE:檢查某個字符串是否匹配指定的字符串模板。其之後可以跟?代表一個字符和*代表任意多個字符兩個通配符。比如"name LIKE '*ac*'",這表示name的值中包含ac則返回YES;"name LIKE '?ac*'",表示name的第2、3個字符爲ac時返回YES。
  • MATCHES:檢查某個字符串是否匹配指定的正則表達式。雖然正則表達式的執行效率是最低的,但其功能是最強大的,也是我們最常用的。

    注:字符串比較都是區分大小寫重音符號的。如:café和cafe是不一樣的,Cafe和cafe也是不一樣的。如果希望字符串比較運算不區分大小寫和重音符號,請在這些運算符後使用[c][d]選項。其中[c]是不區分大小寫,[d]是不區分重音符號,其寫在字符串比較運算符之後,比如:name LIKE[cd] 'cafe',那麼不論name是cafe、Cafe還是café上面的表達式都會返回YES。

4.集合運算符

  • ANY、SOME:集合中任意一個元素滿足條件,就返回YES。
  • ALL:集合中所有元素都滿足條件,才返回YES。
  • NONE:集合中沒有任何元素滿足條件就返回YES。如:NONE person.age < 18,表示person集合中所有元素的age>=18時,才返回YES。
  • IN:等價於SQL語句中的IN運算符,只有當左邊表達式或值出現在右邊的集合中才會返回YES。我們通過一個例子來看一下

    NSArray *filterArray = @[@"ab", @"abc"];
      NSArray *array = @[@"a", @"ab", @"abc", @"abcd"];
      NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", filterArray];
      NSLog(@"%@", [array filteredArrayUsingPredicate:predicate]);

    代碼的作用是將array中和filterArray中相同的元素去除,輸出爲:

    2016-01-07 13:17:43.669 PredicteDemo[6701:136206] (
      a,
      abcd
    )
  • array[index]:返回array數組中index索引處的元素

  • array[FIRST]:返回array數組中第一個元素
  • array[LAST]:返回array數組中最後一個元素
  • array[SIZE]:返回array數組中元素的個數

5.直接量

在謂詞表達式中可以使用如下直接量

  • FALSE、NO:代表邏輯假
  • TRUE、YES:代表邏輯真
  • NULL、NIL:代表空值
  • SELF:代表正在被判斷的對象自身
  • "string"或'string':代表字符串
  • 數組:和c中的寫法相同,如:{'one', 'two', 'three'}。
  • 數值:包括證書、小數和科學計數法表示的形式
  • 十六進制數:0x開頭的數字
  • 八進制:0o開頭的數字
  • 二進制:0b開頭的數字

    6.保留字

    下列單詞都是保留字(不論大小寫)
    ANDORINNOTALLANYSOMENONELIKECASEINSENSITIVECIMATCHESCONTAINSBEGINSWITHENDSWITHBETWEENNULLNILSELFTRUEYESFALSENOFIRSTLASTSIZEANYKEYSUBQUERYCASTTRUEPREDICATEFALSEPREDICATE

    注:雖然大小寫都可以,但是更推薦使用大寫來表示這些保留字


二、謂詞的用法

1.定義謂詞

一般我們使用下列方法來定義一個謂詞

NSPredicate *predicate = [NSPredicate predicateWithFormat:<#(nonnull NSString *), ...#>];

下面我們通過幾個簡單的例子來看看它該如何使用:
首先我們需要定義一個模型,因爲示例中需要用到它
ZLPersonModel.h

#import <Foundation/Foundation.h>

typedef NS_ENUM(NSInteger, ZLPersonSex) {
    ZLPersonSexMale = 0,
    ZLPersonSexFamale
};

@interface ZLPersonModel : NSObject
/** NSString 姓名 */
@property (nonatomic, copy) NSString *name;
/** NSUInteger 年齡 */
@property (nonatomic, assign, readonly) NSUInteger age;
/** ZLPersonSex 性別 */
@property (nonatomic, assign, readonly) ZLPersonSex sex;

+ (instancetype)personWithName:(NSString *)name age:(NSUInteger)age sex:(ZLPersonSex)sex;

@end

ZLPersonModel.m

#import "ZLPersonModel.h"

@implementation ZLPersonModel

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(ZLPersonSex)sex
{
    if (self = [super init]) {
        _name = name;
        _age = age;
        _sex = sex;
    }
    return self;
}

+ (instancetype)personWithName:(NSString *)name age:(NSUInteger)age sex:(ZLPersonSex)sex
{
    return [[self alloc] initWithName:name age:age sex:sex];
}

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

下面讓我們進入正題
例一:(最簡單的使用)

ZLPersonModel *sunnyzl = [ZLPersonModel personWithName:@"sunnyzl" age:29 sex:ZLPersonSexMale];
    ZLPersonModel *jack = [ZLPersonModel personWithName:@"jack" age:22 sex:ZLPersonSexMale];
    //  首先我們來看一些簡單的使用
    //  1.判斷姓名是否是以s開頭的
    NSPredicate *pred1 = [NSPredicate predicateWithFormat:@"name LIKE 's*'"];
    //  輸出爲:sunnyzl:1, jack:0
    NSLog(@"sunnyzl:%d, jack:%d", [pred1 evaluateWithObject:sunnyzl], [pred1 evaluateWithObject:jack]);

    //  2.判斷年齡是否大於25
    NSPredicate *pred2 = [NSPredicate predicateWithFormat:@"age > 25"];
    //  輸出爲:sunnyzl的年齡是否大於25:1, jack的年齡是否大於25:0
    NSLog(@"sunnyzl的年齡是否大於25:%d, jack的年齡是否大於25:%d", [pred2 evaluateWithObject:sunnyzl], [pred2 evaluateWithObject:jack]);

看到這裏我們會發現evaluateWithObject:方法返回的是一個BOOL值,如果符合條件就返回YES,不符合就返回NO。而即使是最簡單的使用也有一些大用處,比如以前我們寫判斷手機號碼郵編等等,像我就喜歡用John Engelhart大神的RegexKitLite,然而由於年代久遠需要導入libicucore.dylib庫(xcode7爲libicucore.tbd)且由於是mrc又需要添加-fno-objc-arc,至此我們才能使用。然而使用謂詞讓我們可以用同樣簡潔的代碼實現相同的功能
例二:
(判斷手機號是否正確)

 - (BOOL)checkPhoneNumber:(NSString *)phoneNumber
{
    NSString *regex = @"^[1][3-8]\\d{9}$";
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
    return [pred evaluateWithObject:phoneNumber];
}

看到這裏是不是感覺好爽,感覺以前所有的正則都可以這麼匹配,但是謂詞匹配正則時也是有缺點的,下面通過一個例子來看一下這個致命的缺點
例三:謂詞匹配正則的缺點
(本意:檢測字符串中是否有特殊字符)

- (BOOL)checkSpecialCharacter:(NSString *)string
{
    NSString *regex = @"[`~!@#$^&*()=|{}':;',\\[\\].<>/?~!@#¥……&*()——|{}【】‘;:”“'。,、?]";
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
    return [pred evaluateWithObject:string];
}

我們想要的效果是字符串中有特殊字符時就返回YES,然而夢想是美好的,現實是殘酷的
讓我們看看這悲催的結局

NSString *testString = @"!";
NSLog(@"是否含有特殊字符:%d", [self checkSpecialCharacter:testString]);
//  當testString爲一個特殊字符時,我們驚喜的發現輸出爲
//  是否含有特殊字符:1

看到這裏我們心裏猛然一喜,這tmd根本沒問題嘛
讓我們修改下testString的值

NSString *testString = @"!~";
NSLog(@"%d", [self checkSpecialCharacter:testString]);
//  我們會發現悲催的結局來了輸出爲
//  是否含有特殊字符:0

再次修改testString的值

NSString *testString = @"abc!~d";
NSLog(@"%d", [self checkSpecialCharacter:testString]);
//  我們會發現輸出爲
//  是否含有特殊字符:0

這總與我們的想法事與願違,看到這裏我們會發現謂詞對正則並不像我們使用NSRegularExpression時匹配的那麼好,究其原因是爲什麼呢?我們用NSRegularExpression時會發現匹配到一個結果時就會存入數組,再從匹配到的位置繼續向下匹配。
然而NSPredicate並不會做這樣的自動操作,我們最終發現在NSPredicate輸入[`~!@#$^&*()=|{}':;',\[\].<>/?~!@#¥……&*()——|{}【】‘;:”“'。,、?]正則表達式時和寫成^[`~!@#$^&*()=|{}':;',\[\].<>/?~!@#¥……&*()——|{}【】‘;:”“'。,、?]$的效果是一樣的。
所以通過這個例子我們總結出來,只有在正則表達式爲^表達式$時才使用謂詞,而不是所有情況都使用。

當然上例中我們可以用一個投機取巧的方法實現(但是僅能用於匹配是否包含特殊符號,而無法像NSRegularExpression那樣對這些特殊符號進行復雜操作)
我們可以將- (BOOL)checkSpecialCharacter:(NSString *)string更改爲:

- (BOOL)checkSpecialCharacter:(NSString *)string
{
    NSString *regex = @".*[`~!@#$^&*()=|{}':;',\\[\\].<>/?~!@#¥……&*()——|{}【】‘;:”“'。,、?].*";
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
    return [pred evaluateWithObject:string];
}

其實上述方法也只是匹配了一次,只不過我們將它的範圍擴大了。
那麼我們是不是因爲這一點就摒棄它了呢,答案是否定的。因爲雖然NSPredicate有這麼一點瑕疵,但是它總體帶給我們的便利其實除了正則表達式匹配時的這個問題外是更多的。

2.使用謂詞過濾集合

此部分是我們需要掌握的重點,因爲從這裏我們就可以看到謂詞的真正的強大之處
其實謂詞本身就代表了一個邏輯條件,計算謂詞之後返回的結果永遠爲BOOL類型的值。而謂詞最常用的功能就是對集合進行過濾。當程序使用謂詞對集合元素進行過濾時,程序會自動遍歷其元素,並根據集合元素來計算謂詞的值,當這個集合中的元素計算謂詞並返回YES時,這個元素纔會被保留下來。請注意程序會自動遍歷其元素,它會將自動遍歷過之後返回爲YES的值重新組合成一個集合返回。
其實類似於我們使用tableView設置索引時使用的下段代碼

- (NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
    return [self.cityGroup valueForKey:@"title"];
}

中的[self.cityGroup valueForKey:@"title"]。它的作用是遍歷所有title並將得到的值組成新的數組。

  • NSArray提供瞭如下方法使用謂詞來過濾集合
    - (NSArray<ObjectType> *)filteredArrayUsingPredicate:(NSPredicate *)predicate:使用指定的謂詞過濾NSArray集合,返回符合條件的元素組成的新集合
  • NSMutableArray提供瞭如下方法使用謂詞來過濾集合
    - (void)filterUsingPredicate:(NSPredicate *)predicate:使用指定的謂詞過濾NSMutableArray剔除集合中不符合條件的元素
  • NSSet提供瞭如下方法使用謂詞來過濾集合
    - (NSSet<ObjectType> *)filteredSetUsingPredicate:(NSPredicate *)predicate NS_AVAILABLE(10_5, 3_0):作用同NSArray中的方法
  • NSMutableSet提供瞭如下方法使用謂詞來過濾集合
    - (void)filterUsingPredicate:(NSPredicate *)predicate NS_AVAILABLE(10_5, 3_0):作用同NSMutableArray中的方法。
    通過上面的描述可以看出,使用謂詞過濾不可變集合和可變集合的區別是:過濾不可變集合時,會返回符合條件的集合元素組成的新集合;過濾可變集合時,沒有返回值,會直接剔除不符合條件的集合元素

下面讓我們來看幾個例子:
例一:

NSMutableArray *arrayM = [@[@20, @40, @50, @30, @60, @70] mutableCopy];
    //  過濾大於50的值
    NSPredicate *pred1 = [NSPredicate predicateWithFormat:@"SELF > 50"];
    [arrayM filterUsingPredicate:pred1];
    NSLog(@"arrayM:%@", arrayM);

    NSArray *array = @[[ZLPersonModel personWithName:@"Jack" age:20 sex:ZLPersonSexMale],
                       [ZLPersonModel personWithName:@"Rose" age:22 sex:ZLPersonSexFamale],
                       [ZLPersonModel personWithName:@"Jackson" age:30 sex:ZLPersonSexMale],
                       [ZLPersonModel personWithName:@"Johnson" age:35 sex:ZLPersonSexMale]];
    //  要求取出包含‘son’的元素
    NSPredicate *pred2 = [NSPredicate predicateWithFormat:@"name CONTAINS 'son'"];
    NSArray *newArray = [array filteredArrayUsingPredicate:pred2];
    NSLog(@"%@", newArray);

輸出爲

2016-01-07 16:50:09.510 PredicteDemo[13660:293822] arrayM:(
    60,
    70
)
2016-01-07 16:50:09.511 PredicteDemo[13660:293822] (
    "[name = Jackson, age = 30, sex = 0]",
    "[name = Johnson, age = 35, sex = 0]"
)

從這個例子我們就可以看到NSPredicate有多麼強大,如果讓我們用其他的方法來實現又是一大堆if...else
讓我們來回顧一下上面的從第二個數組中去除第一個數組中相同的元素
例二:

NSArray *filterArray = @[@"ab", @"abc"];
    NSArray *array = @[@"a", @"ab", @"abc", @"abcd"];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", filterArray];
    NSLog(@"%@", [array filteredArrayUsingPredicate:predicate]);

輸出爲:

2016-01-07 13:17:43.669 PredicteDemo[6701:136206] (
    a,
    abcd
)

如果我們不用NSPredicate的話,肯定又是各種if...elsefor循環等等。可以看出NSPredicate的出現爲我們節省了大量的時間和精力。

3.在謂詞中使用佔位符參數

我們上面所有的例子中謂詞總是固定的,然而我們在現實中處理變量時決定了謂詞應該是可變的。下面我們來看看如果讓謂詞變化起來。
首先如果我們想在謂詞表達式中使用變量,那麼我們需要了解下列兩種佔位符

  • %K:用於動態傳入屬性名
  • %@:用於動態設置屬性值
    其實相當於變量名與變量值
    除此之外,還可以在謂詞表達式中使用動態改變的屬性值,就像環境變量一樣

    NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF CONTAINS $VALUE"];

    上述表達式中,$VALUE是一個可以動態變化的值,它其實最後是在字典中的一個key,所以可以根據你的需要寫不同的值,但是必須有$開頭,隨着程序改變$VALUE這個謂詞表達式的比較條件就可以動態改變。
    下面我們通過一個例子來看看這三個重要的佔位符應該如何使用
    例一:

    NSArray *array = @[[ZLPersonModel personWithName:@"Jack" age:20 sex:ZLPersonSexMale],
                         [ZLPersonModel personWithName:@"Rose" age:22 sex:ZLPersonSexFamale],
                         [ZLPersonModel personWithName:@"Jackson" age:30 sex:ZLPersonSexMale],
                         [ZLPersonModel personWithName:@"Johnson" age:35 sex:ZLPersonSexMale]];
      //  定義一個property來存放屬性名,定義一個value來存放值
      NSString *property = @"name";
      NSString *value = @"Jack";
      //  該謂詞的作用是如果元素中property屬性含有值value時就取出放入新的數組內,這裏是name包含Jack
      NSPredicate *pred = [NSPredicate predicateWithFormat:@"%K CONTAINS %@", property, value];
      NSArray *newArray = [array filteredArrayUsingPredicate:pred];
      NSLog(@"newArray:%@", newArray);
    
      //  創建謂詞,屬性名改爲age,要求這個age包含$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);

    輸出爲

    2016-01-07 17:28:02.062 PredicteDemo[14542:309494] newArray:(
      "[name = Jack, age = 20, sex = 0]",
      "[name = Jackson, age = 30, sex = 0]"
    )
    2016-01-07 17:28:02.063 PredicteDemo[14542:309494] newArray1:(
      "[name = Jackson, age = 30, sex = 0]",
      "[name = Johnson, age = 35, sex = 0]"
    )
    2016-01-07 17:28:02.063 PredicteDemo[14542:309494] newArray2:(
      "[name = Johnson, age = 35, sex = 0]"
    )

    從上例中我們主要可以看出來%K和$VALUE的含義。
    那麼至此NSPredicate就到到此介紹完畢。

發佈了34 篇原創文章 · 獲贊 1 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章