HOME » Natsu note » 古い投稿 » NSPredicate : テンプレートを利用した生成方法(パフォーマンス改善)

NSPredicate : テンプレートを利用した生成方法(パフォーマンス改善) 2010/04/01/|古い投稿,

この記事は情報が古い可能性があります。参照する際にはご注意ください。

Core Dataのデータをフェッチするとき、特定の条件を指定したければNSPredicateを使う。NSPredicateは、Core Dataに限らず、NSArrayなどにも利用可能。このNSPredicateを作成する際の小ワザ的な話として、テンプレートを使う方法が以下のドキュメントに載っていた。

Max Dev Center: Core Data Snippets: Fetching Managed Objects


… NSPredicate’s predicateWithFormat: method is typically the easiest way to use a predicate (as shown in “Fetch with a Predicate”), but it’s not the most efficient way to create the predicate itself. The predicate format string has to be parsed, arguments substituted, and so on. For performance-critical code, particularly if a given predicate is used repeatedly, you should consider other ways to create the predicate. For a predicate that you might use frequently, the easiest first step is to create a predicate template. You might create an accessor method that creates the predicate template lazily on demand:

NSPredicateを生成する最も簡単な方法は、predicateWithFormat:を利用することだが、文字列のパースその他の処理のせいでパフォーマンスはあまりよくない。何度も繰り返し使われるようなNSPredicateは、テンプレートを使って生成したほうが賢いようだ。実際にパフォーマンスの計測はしていないのでどの程度の効果があるかは何とも言えないのだけど、覚えておいて損はなさそうなので、以下にテンプレートを利用したNSPredicateの生成方法をまとめる。

例として、以下のようなKey-Valueを持つオブジェクトをCore DataまたはNSArrayで管理している場合を考えてみる。

key valueの型
age NSNumber
name NSString

ここで、年齢で条件指定するためのagePredicateTemplateと、名前(文字列)で条件指定するためのnamePredicateTemplateを作ってみる。まず、NSPredicate*型のxxxPredicateTemplateを用意する。 アクセサメソッドを利用できるようにヘッダでプロパティ宣言もしておく。

@interface ManagedObjectList {
	@private
	NSPredicate *_agePredicateTemplate;
	NSPredicate *_namePredicateTemplate;
}
@property (nonatomic, retain) NSPredicate *agePredicateTemplate;
@property (nonatomic, retain) NSPredicate *namePredicateTemplate;
@end

次に、実装部分。まず、agePredicateTemplateとnamePredicateTemplateを実装し、ゲッターメソッドを以下のように作る。

@synthesize agePredicateTemplate = _agePredicateTemplate;
@synthesize namePredicateTemplate = _namePredicateTemplate;

- (NSPredicate *)agePredicateTemplate {
	if (_agePredicateTemplate == nil) {
		_agePredicateTemplate = [[NSPredicate predicateWithFormat:@"$minAge <= age AND age < $maxAge"] retain];
	}
	return _agePredicateTemplate;
}

- (NSPredicate *)namePredicateTemplate {
	if (_namePredicateTemplate == nil) {
		_namePredicateTemplate = [[NSPredicate predicateWithFormat:@"name like $name"] retain];
	}
	return _namePredicateTemplate;
}

これでテンプレートは完成。ポイントとなるのは、$minAge, $maxAge, $name のように、"$" のついた表現。ここが、このテンプレートを使う際の「キー」になる。テンプレートからNSPredicateを生成するためには、predicateWithSubstitutionVariables:メソッドを利用し、引数にこのキーと値を組み合わせたNSDictionaryを渡す。

では、実際にテンプレートを使ってNSPredicateを生成してみる。例えば、「10際以上20歳未満」という条件を作りたければ以下のようになる。

NSNumber *min = [NSNumber numberWithInteger:10];
NSNumber *max = [NSNumber numberWithInteger:20];
NSDictionary *variables = [NSDictionary dictionaryWithObjectsAndKeys:min, @"minAge", max, @"maxAge", nil];
NSPredicate *agePred = [[self agePredicateTemplate] predicateWithSubstitutionVariables:variables];

同様に、namePredicateTemplateは以下のように使えばよい。例えば、「名前がTom」という条件は以下のように作る。

NSString *name = @"Tom";
NSDictionary *variables = [NSDictionary dictionaryWithObject:name forKey:@"name"];
NSPredicate *namePred = [[self namePredicateTemplate] predicateWithSubstitutionVariables:variables];

もしこれらをテンプレートなしで生成した場合、それぞれ以下のようになる。

<pre><code>
NSPredicate *agePred = [NSPredicate predicateWithFormat:@"%@ <= age AND age < %@", min, max];
NSPredicate *namePred = [NSPredicate predicateWithFormat:@"name like %@", name];

テンプレートを使うことで、このpredicateWithFormat:を呼び出す回数を最小限に抑えることができ、より効率的になるとのことだ。

冒頭にも書いたとおり、NSPredicateはCore Dataのフェッチ以外にNSArrayでも利用できる。NSArrayにNSDictionaryオブジェクトを格納して使っていて、特定のオブジェクトだけ取り出したければNSPredicateの出番。とても便利なので覚えておいて損はないはず。なお、NSArrayにNSPredicateを適用するときは以下のメソッドを使う。

- (NSArray *)filteredArrayUsingPredicate:(NSPredicate *)predicate

二段階に分けてKey-Value Codingをする形となっており一瞬わかりにくさを感じるかもしれないが、一度理解できてしまえばさほど難しいものではない。まずは、上の例や下記ドキュメントなどから、テンプレートを使う場合と使わない場合で、どの変数がどの変数と一致しているかを見比べてみると分かりやすいかもしれない(実際、私はそうやって理解した・・・)。

参照;
Max Dev Center: Core Data Snippets: Fetching Managed Objects
Mac Dev Center: Predicate Programming Guide: Creating Predicate