こんにちは。
今日は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 オブジェクトの生成を分離することに成功しています。