-objectForKey: を -[NSManagedObjectModel entitiesByName] によって返されるディクショナリに送信すると、渡されたキーが @“foo” などのリテラル定数である場合、-objectForKey: は期待される結果を返します。しかし、その代わりに @“foo” と等しくなるように構築された値を持つローカル変数を渡すと、-objectForKey: は Mac OS X 10.9 で nil を返します。Mac OS X 10.8 では、キーが定数であるかどうかに関係なく、期待値を返します。
この問題はおそらく、10.9 で -entitiesByName が NSKnownKeysDictionary2 という NSDictionary のサブクラスを返すという事実によるものです。10.8 では、NSKnownKeysDictionary1 を返します。私の推測では
- 最適化中に、コンパイラは重複する定数文字列を 1 つに結合します。
- NSKnownKeysDictionary2 は、キーがそのような定数であるという前提に基づいて最適化されているため、-isEquals: の代わりにポインターの等価性を使用します。
ドキュメントによると、-[NSManagedObjectModel entitiesByName] は NSDictionaryを返します。これは、-isEquals: を使用してキーを比較するように文書化されています…</p>
ディクショナリ内では、キーは一意です。つまり、1 つのディクショナリ内の 2 つのキーが等しいことはありません (isEqual: によって決定されます)。
これは Mac OS X 10.9 のバグだと思います。NSKnownKeysDictionary2 を返すように最適化したときに、-[NSManagedObjectModel entitiesByName] がパブリック メソッドであることに誰かが気付かなかったのではないかと思います。ほとんどの開発者はエンティティ名に定数文字列を使用しているため、このバグは開発者プレビューをすり抜けて、誰も気付かなかった.
私は間違っていますか、それとも「バグを報告」する必要がありますか?
ありがとう、
ジェリー・クリノック
これを示すコード (私のプロジェクトから、申し訳ありません) は以下のとおりです。これは、NSManagedObject の個人的な「スーパー サブクラス」からのものであり、NSManagedObject では実行できない私のお気に入りの機能をすべて実行します。
コードの「最初の試行」からわかるように、メソッド +[NSEntityDescription entityForName:inManagedObjectContext:] の結果も、この明らかなバグの影響を受けます。
アプリをビルドして Mac OS X 10.8 で実行すると、ログに記録されます
- 最初の試行は Stark_entity で機能しました
- 最初の試行は Ixporter_entity で機能しました
しかし、同じデータを使用して 10.9 で同じビルドを実行すると、1 回目、2 回目、3 回目の試行は明らかに失敗します。
- Stark_entityで4回目の試行が機能しました
- 4 回目の試行は Ixporter_entity で機能しました
コード:
// How I like to name my entities…
+ (NSString*)entityNameForClass:(Class)class {
return [NSStringFromClass(class) stringByAppendingString:@"_entity"] ;
}
+ (NSEntityDescription*)entityDescription {
// Stuff we'll need
NSArray* bundles = [NSArray arrayWithObject:[NSBundle mainBundle]] ;
NSManagedObjectModel* mom = [NSManagedObjectModel mergedModelFromBundles:bundles] ;
NSString* entityName = [self entityNameForClass:self] ; // See above
// What we want
NSEntityDescription* entityDescription = nil ;
// First try.
NSPersistentStoreCoordinator* psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom] ;
NSManagedObjectContext* moc = [[NSManagedObjectContext alloc] init] ;
[moc setPersistentStoreCoordinator:psc] ;
entityDescription = [NSEntityDescription entityForName:entityName
inManagedObjectContext:moc] ;
[psc release] ;
[moc release] ;
NSDictionary* entities = [mom entitiesByName] ;
NSLog(@"entities is an %@", [entities className]) ;
NSLog(@"entityName is an %@", [entityName class]) ;
NSLog(@"entities is a dictionary? = %hhd”,
[entities isKindOfClass:[NSDictionary class]]) ;
if (entityDescription) {
NSLog(@"1st try worked for %@", entityName) ;
}
else {
// Second try
entityDescription = [entities objectForKey:entityName] ;
if (!entityDescription) {
// Third try
NSString* entityNameCopy = [entityName copy] ;
entityDescription = [entities objectForKey:entityNameCopy] ;
[entityNameCopy release] ;
}
if (!entityDescription) {
// Fourth try
NSSet* candidateEntityNames = [NSSet setWithObjects:
@"Stark_entity",
@"Ixporter_entity",
nil] ;
for (NSString* candidate in candidateEntityNames) {
if ([entityName isEqualToString:candidate]) {
entityDescription = [entities objectForKey:candidate] ;
if (entityDescription) {
NSLog(@"4th try worked for %@", entityName) ;
break ;
}
}
}
}
}
if (!entityDescription) {
NSLog(@"Internal Error 561-3831 for %@", entityName) ;
}
return entityDescription ;
}