3

我构建了一个简单的问答游戏,可以在 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 的属性?
4

0 回答 0