NSPredicateで遊ぶ

こんにちは。

今日はC#のLINQと少しだけ似ている NSPredicate のお話。なぜか『詳解 Objective-C 2.0 第3版』には載っていないクラスですが、たいへん便利な代物です。

ちなみにくろねこさんが同じテーマの記事をQiitaに投稿しており、今さら感が漂っておりますが僕は気にしない。

用途としては、ある集成オブジェクト(配列とか)から、条件に合致する要素を抽出する際に使用します。LINQでは内部結合や外部結合などの集合演算も可能でしたが、NSPredicate の目的はあくまでデータのフィルタリングですので、ほぼ別物と考えた方がよいでしょう。

いくつか例を書いてみます。

#import <Foundation/Foundation.h>

// キー文字列の定義
NSString * const ID   = @"id";
NSString * const NAME = @"name";
NSString * const SEX  = @"sex";

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        // こんな配列がありました
        NSArray *list= @[ @{ID: @1, NAME: @"tercel_s", SEX: @"Male"},
                          @{ID: @2, NAME: @"linehat" , SEX: @"Male"},
                          @{ID: @3, NAME: @"pi_cro_s", SEX: @"Female"} ];
        NSLog(@"%@", list.description);
        
        // 使い回し用変数
        NSPredicate *predicate;
        NSArray *result;
        
        // ①名前に「e」を含む情報を抽出
        predicate = [NSPredicate predicateWithFormat:@"name like '*e*'"];
        result = [list filteredArrayUsingPredicate:predicate];
        NSLog(@"%@", result.description);
                
        // ②IDが1または3の情報を抽出
        predicate = [NSPredicate predicateWithFormat:@"id == 1 or id == 3"];
        result = [list filteredArrayUsingPredicate:predicate];
        NSLog(@"%@", result.description);
        
        // ③名前の最後が t で終わるものを、正規表現を用いて抽出
        predicate = [NSPredicate predicateWithFormat:@"name matches '.*t$'"];
        result = [list filteredArrayUsingPredicate:predicate];
        NSLog(@"%@", result.description);
    }
    return 0;
}

ここでは、NSDictionaryを配列にしてテーブルっぽいデータ構造を作っています。

出力時に見やすくなるよう、main 関数の直前にこういうコードを書いてみます。

@implementation NSArray(TERCEL)
-(NSString *) description {
    NSMutableString *result = [NSMutableString stringWithString:@"\n"];
    
    // 見出し
    [result appendString:[NSString stringWithFormat:@"%@ \t%@ \t%@\n",
                          ID, NAME, SEX]];
    [result appendString:@"----------------------------------------\n"];
    
    // 明細
    for (id list in self)
        [result appendString:[NSString stringWithFormat:@"%@ \t%@ \t %@\n",
                              list[ID], list[NAME], list[SEX] ]];
    
    [result appendString:@"\n"];
    return result;
}
@end

で、実行するとこんな感じ。

2014-01-10 13:33:24.490 NSPredicateSample[2981:303] 
id name sex
----------------------------------------
1 tercel_s 	 Male
2 linehat 	 Male
3 pi_cro_s 	 Female

2014-01-10 13:33:24.494 NSPredicateSample[2981:303] 
id name sex
----------------------------------------
1 tercel_s 	 Male
2 linehat 	 Male

2014-01-10 13:33:24.494 NSPredicateSample[2981:303] 
id name sex
----------------------------------------
1 tercel_s 	 Male
3 pi_cro_s 	 Female

2014-01-10 13:33:24.494 NSPredicateSample[2981:303] 
id name sex
----------------------------------------
2 linehat 	 Male

Program ended with exit code: 0

はい。

こんな感じで、ちゃんと抽出できていることがわかります。


ところで。

くろねこさんも書いていますが、以下のコードは抽出条件をハードコーディングしており、あまり柔軟性があるとは言えません。

        // ①名前に「e」を含む情報を抽出
        predicate = [NSPredicate predicateWithFormat:@"name like '*e*'"];
        result = [list filteredArrayUsingPredicate:predicate];
        NSLog(@"%@", result.description);

そこで、くろねこさんはこんなふうに書き換えています。

        // (①の書き換え例):
        NSDictionary *dictionary = @{@"pattern": @"*e*"};
        NSPredicate *template = [NSPredicate predicateWithFormat:@"%K like $pattern",
                                 NAME];
        predicate = [template predicateWithSubstitutionVariables:dictionary];
        result = [list filteredArrayUsingPredicate:predicate];
        NSLog(@"%@", result.description);

これは、検索パターンを NSDictionary に記述することで、検索パターンの定義とNSPredicate オブジェクトの生成を分離することに成功しています。

Copyright (c) 2012 @tercel_s, @iTercel, @pi_cro_s.