我构建了一个简单的问答游戏,可以在 iOS 5.1、6.0 和 6.1 上运行。我使用核心数据,我有这两个对象:
@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
这个想法是每个问题都只有一个类别(例如,娱乐或体育)。Category-Question 关系是一对多的;在我的样本数据中,有 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 代表我的持久存储。诚然,这可能不是保持这种状态的最佳方法,但我不相信这是我问题的根源。
接下来,用户可以按下新游戏按钮。然后执行此提取:
- (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 并离开设置窗口后,我在按下 New Game 之前等待了大约 10 秒,那么一切正常;loadBatchOfQuestions 中的 fetch 基于当前的 Category 数据返回正确的行。但是,如果我在关闭设置窗口后立即按下新游戏,我会看到旧数据(即我查询了错误的问题)。
对于我在上面添加的 NSLog 语句:
- 在 okPressed 中,我总是看到 wasSaveSuccessful=YES 和 areChangesPending=NO(如我所料)
- 对于 loadBatchOfQuestions 中的 TEST #1,我获取了所有类别行(在本例中为 3)并验证了我看到的是最新数据
- 对于 loadBatchOfQuestions 中的 TEST #2,我获取一个问题行以查看它是否显示正确的类别数据;确实如此。
然而,随后的 fetch 返回旧数据。
这是我认为正在发生的事情:
- 当我在 okPressed 中调用 save: 时,它实际上是将更改写入内存缓存。然后这些更改会在 3-10 秒内异步写入 Core Data 持久存储。
- TEST #1 和 TEST #2 中的提取是非常基本的;他们只是用一个简单的谓词获取对象(在 TEST #1 的情况下没有谓词,因为我想要所有行,在 TEST #2 的情况下是 @"questionID = %ld")。对于这些情况,提取可以在从持久存储中检索数据之前检查缓存中的当前数据。(NSManagedObjectContext.executeFetchRequest 的文档说“如果上下文中的对象已被修改,则根据其修改后的状态评估谓词,而不是根据持久存储中的当前状态。因此,如果上下文中的对象已被修改,则如果它满足获取请求的条件,即使更改尚未保存到存储并且存储中的值不满足条件,请求也会检索它。相反,
- 但是,我在 loadBatchOfQuestions 中的主要谓词非常复杂,以至于在评估缓存时提取不会/不能查看缓存,因此我看到的是旧数据。但是,如果我等待的时间足够长,那么在执行此 fetch 时,持久存储将已更新。
我的问题:
- 我的分析有意义吗?或者有人在我的代码中看到其他错误/缺陷吗?
- 如果我是正确的,有没有办法解决这个问题?也许调用一个委托方法告诉我数据何时“真正”保存到持久存储?或者我需要设置的 UIManagedDocument 或 NSManagedObjectContext 的属性?