Edited at

NSPredicate で BOOL 型プロパティをテストするときの落とし穴

More than 5 years have passed since last update.

ある NSManagedObject のサブクラスが BOOL 型のプロパティ aBoolProperty を持つとする。このプロパティが偽であるかどうかをテストするとき、以下のような NSPredicate を書きたくなるかもしれない:

anObject.aBoolProperty = NO;

NSPredicate *isNO = [NSPredicate predicateWithFormat:@"aBoolProperty == NO"];
[isNO evaluateWithObject:anObject]; // => YES

(注: predicate には比較演算子が必要なので @"!aBoolProperty" とは書けない)

厳密に NO と比較したいならこれで構わないが、 aBoolProperty偽だが NO と一致しない 場合もあることに気をつけよう。

Core Data が内部的に扱う aBoolProperty の値は @YES または @NO または nil である。これは BOOL 型の getter メソッドを介してアクセスする限り問題にならないが、内部値を直接扱う predicate では nil != @NO であることに注意しなければならない。

[anObject setPrimitiveValue:nil forKey:@"aBoolProperty"];

// getter は内部値が nil なら偽を返す
NSAssert(!anObject.aBoolProperty, @"");

// predicate では nil は NO と一致しない
NSPredicate *isNO = [NSPredicate predicateWithFormat:@"aBoolProperty == NO"];
[isNO evaluateWithObject:anObject]; // => *NO*

これが望み通りの挙動でないなら、 NO と一致するかどうかではなく YES でないことをテストすればよい:

[anObject setPrimitiveValue:nil forKey:@"aBoolProperty"];

NSPredicate *isNO = [NSPredicate predicateWithFormat:@"aBoolProperty != YES"];
[isNO evaluateWithObject:anObject]; // => YES