iOS 5.1、6.0、および 6.1 で動作する簡単なトリビア ゲームを作成しました。Core Data を使用しており、次の 2 つのオブジェクトがあります。
@interface Category : NSManagedObject
@property (nonatomic, retain) NSString * category;
@property (nonatomic, retain) NSNumber * difficulty;
@property (nonatomic, retain) NSSet *questions;
@end
と
@interface Question : NSManagedObject
@property (nonatomic, retain) NSNumber * disabled;
@property (nonatomic, retain) NSNumber * displayTypeEasy;
@property (nonatomic, retain) NSNumber * displayTypeHard;
@property (nonatomic, retain) NSNumber * displayTypeMedium;
@property (nonatomic, retain) NSNumber * questionID;
@property (nonatomic, retain) Category *category;
@end
各質問には 1 つのカテゴリ (エンターテイメントやスポーツなど) があるという考え方です。カテゴリと質問の関係は 1 対多です。サンプル データには、3 つのカテゴリと 20 の質問があります。
ユーザーが再生したいカテゴリを変更したり、特定のカテゴリの難易度を変更したりできるようにする設定ウィンドウがあります (つまり、このウィンドウでは、カテゴリ オブジェクトが変更されます)。ユーザーが [OK] を押すと、次のコードが実行されます。
- (IBAction)okPressed:(UIButton *)sender {
NSError * error;
BOOL wasSaveSuccessful = [[Constants database].managedObjectContext save:&error];
NSLog(@"wasSaveSuccessful:%@ areChangesPending:%@", wasSaveSuccessful ? @"YES" : @"NO",
[[Constants database].managedObjectContext hasChanges] ? @"YES" : @"NO");
}
ところで:「[Constants database]」は、永続ストアを表す UIManagedDocument です。確かに、これはこれを維持するための最良の方法ではないかもしれませんが、それが私の問題の原因であるとは思いません.
次に、ユーザーは New Game ボタンを押すことができます。次に、このフェッチが実行されます。
- (void) loadBatchOfQuestions
{
if ([self.questionBatch count] == 0) { // self.questionBatch is an NSMutableArray
// TEST #1
for (Category * c in [[Category allCategories] objectEnumerator])
NSLog(@"category:%@ difficulty:%d", c.category, [c.difficulty integerValue]);
// TEST #2
Question * q1 = [Question queryQuestionWithQuestionID:50150];
NSLog(@"q1 difficulty:%d", [q1.category.difficulty integerValue]);
NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Question"];
request.predicate = [NSPredicate predicateWithFormat:
@"(disabled == NO)"
@"AND (questionID > %ld)"
@"AND (((category.difficulty == %i) AND (displayTypeEasy != NULL))"
@" OR ((category.difficulty == %i) AND (displayTypeMedium != NULL))"
@" OR ((category.difficulty == %i) AND (displayTypeHard != NULL)))",
MAX_QUESTIONID_FOR_TEST_QUESTIONS,
DIFFICULTY_EASY, DIFFICULTY_MEDIUM, DIFFICULTY_HARD];
NSError * error;
[self.questionBatch addObjectsFromArray:[[Constants database].managedObjectContext executeFetchRequest:request error:&error]];
}
問題は次のとおりです。[OK] を押して設定ウィンドウを閉じた後、10 秒ほど待ってから [新しいゲーム] を押すと、すべてうまくいきます。loadBatchOfQuestions のフェッチは、現在のカテゴリ データに基づいて正しい行を返します。ただし、設定ウィンドウを閉じた直後に [新しいゲーム] を押すと、古いデータが表示されます (つまり、間違った質問をクエリします)。
上記で追加した NSLog ステートメントの場合:
- okPressed では、wasSaveSuccessful=YES と areChangesPending=NO が常に表示されます (予想どおり)。
- loadBatchOfQuestions の TEST #1 では、すべてのカテゴリ行 (この場合は 3 行) をフェッチし、最新のデータが表示されることを確認しました。
- loadBatchOfQuestions の TEST #2 では、1 つの質問行をフェッチして、正しいカテゴリ データが表示されているかどうかを確認します。します。
それにもかかわらず、後続のフェッチは古いデータを返します。
これが私が起こっていると思うことです:
- save: を呼び出すと、okPressed で実際にメモリ内キャッシュに変更が書き込まれます。これらの変更は、3 ~ 10 秒かけてコア データの永続ストアに非同期的に書き込まれます。
- TEST #1 と TEST #2 のフェッチはかなり基本的なものです。それらは単純な述語を使用してオブジェクトをフェッチするだけです (すべての行が必要なため、TEST #1 の場合は述語はありません。TEST #2 の場合は @"questionID = %ld" です)。このような場合、フェッチは永続ストアからデータを取得する前に、キャッシュ内の現在のデータをチェックできます。(NSManagedObjectContext.executeFetchRequest のドキュメントには、「コンテキスト内のオブジェクトが変更された場合、述語は、永続ストア内の現在の状態ではなく、変更された状態に対して評価されます。したがって、コンテキスト内のオブジェクトがそのように変更された場合、フェッチ リクエストの条件を満たしている場合、変更がストアに保存されておらず、ストア内の値が条件を満たさない場合でも、リクエストはそれを取得します。
- ただし、loadBatchOfQuestions の主な述語は非常に複雑であるため、キャッシュを評価するときにキャッシュを参照しない/参照できないため、代わりに古いデータが表示されます。しかし、十分に長く待つと、このフェッチが実行されるまでに永続ストアが更新されます。
私の質問:
- 私の分析は意味がありますか?または、誰かが私のコードに他のバグ/欠陥を見ていますか?
- 私が正しければ、これを回避する方法はありますか?おそらく、データが永続ストアに「実際に」保存されたときに通知するデリゲート メソッドの呼び出しでしょうか。それとも、設定する必要がある UIManagedDocument または NSManagedObjectContext のプロパティですか?