NSFetchedResults保存持久性MOC后返回旧数据

时间:2015-07-09 21:42:54

标签: ios core-data

上下文

我有一个简单的核心数据堆栈:MainQueueMOC -> PrivateBackgroundMOC -> PersistentStoreCoordinator由我的TTPersistenceManager管理,如下所示:

typedef NS_ENUM(NSInteger, TTPersistenceType) {
    TTPersistenceTypeInMemory,
    TTPersistenceTypeSQLite
};

@interface TTPersistenceManager : NSObject

@property (strong, nonatomic, readonly) NSManagedObjectContext *managedObjectContext; // this is the MainQueueMOC

- (id)initWithPersistenceType:(TTPersistenceType)persistenceType;
- (void)initializeCoreData;

- (void)save;
- (void)persist;

目前我们只使用内存商店。

受到article from Marcus Zarra的启发。因此,MainQueueMOC是真实的唯一来源,而PrivateBackgroundMOC仅用于在后台保存到商店,并且它永远不会公开。如果您阅读该文章,您会注意到我添加了一个名为persist的方法,savepersist之间的差异是:

  • save,使用performBlockAndWait
  • 保存MainQueueMOC
  • persist,使用performBlockAndWait保存MainQueueMOC,使用performBlock
  • 保存PrivateBackgroundMOC

我之所以这么做是因为这两段:

  

作为一项规则,每当我们离开我们要调用的应用程序时,保存在持久性控制器上。这保证了如果我们在暂停时被杀,我们就不会丢失数据。

     

在大多数情况下,这是您需要在主要和私人环境中调用保存的唯一地方。

因此,save是保存单一事实来源的方法,我们在对托管对象进行任何更改后调用它,而persist仅在转到后台时调用应用程序代理或即将被终止以保存对商店的所有更改。

除以下问题外,此方法正常。

问题

我们有NSFetchedResultsController这样的谓词:

item.kind = "relationship" AND item.relationship.archived == NO

我们不会显示与存档关系相关联的项目。用户可以在行上滑动以归档它,该行会更改relationship.archived = @YES,调用[TTPersistenceManager save],然后重新获取NSFetchedResultsController,该项目将从列表中消失。这很有效。

直到我们第一次进入背景。

- (void)applicationDidEnterBackground:(UIApplication *)application {
    [self.persistenceController persist];
}

调用persist后,如果用户滑动到存档,则项目不会从列表中消失。

relationship.archived = @YES; // item at index 0 is associated with this object
[self.persistenceManager save];
[self.fetchedResultsController performFetch:&error]; // Works, no error

item = [[self.fetchedResultsController] fetchedObjects] firstObject];
NSLog(@"item is archived %d", item.relationship.archived);
// prints: item is archived 1

relationship对象的archived属性设置为YES,但抓取仍然会返回它。

可能的解决方案

我找到了两种可能的解决方案。但我想选择更正确的一个,无论我们使用内存存储还是sqlite,都能在所有情况下按预期工作。

\ 1。添加updatedAt属性到item我们每次更新relationship时设置当前日期:

 relationship.item.updatedAt = [NSDate date];
 relationship.archived = @YES;
 [self.persistenceManager save];

\ 2。始终致电persist而不是save

relationship.archived = @YES;
[self.persistenceManager persist];

什么是正确的方法?

我的假设是我们在进入后台时只保存连接到持久存储的MOC吗?

为什么在item中添加一个甚至不在谓词中使用的属性?

更新:源代码

@implementation TTPersistenceManager

- (id)initWithPersistenceType:(TTPersistenceType)persistenceType {
    self = [super init];
    if (self) {
        _persistenceType = persistenceType;
    }
    return self;
}

- (void)initializeCoreData {
    FCYAssert(!self.managedObjectModel, @"CoreData has already been initialized");

    NSBundle *bundle = [NSBundle bundleForClass:[self class]];
    self.managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:@[bundle]];
    self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];

    NSError *error = nil;
    NSPersistentStore *store = [self.persistentStoreCoordinator addPersistentStoreWithType:[self storageType] configuration:nil URL:nil options:nil error:&error];
    FCYAssert(store != nil, @"Failed create persistent store: %@\n%@", [error localizedDescription], [error userInfo]);

    self.persistentStoreManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    self.persistentStoreManagedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
    self.persistentStoreManagedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;

    self.managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    self.managedObjectContext.parentContext = self.persistentStoreManagedObjectContext;
    self.managedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
}

- (void)persist {
    if (![self saveContext:self.managedObjectContext]) return;
    [self saveContext:self.persistentStoreManagedObjectContext];
}

- (void)save {
    [self saveContext:self.managedObjectContext];
}

#pragma mark - Private

- (NSString *)storageType {
    if (self.persistenceType == TTPersistenceTypeSQLite) return NSSQLiteStoreType;
    return NSInMemoryStoreType;
}

- (BOOL)saveContext:(NSManagedObjectContext *)context {
    NSError *error = nil;
    BOOL didSave = [self saveContext:context error:&error];

    if (!didSave) {
        TTLogError(@"Error saving context: %@\n\nUser Info:\n%@\n\nCall Stack:\n%@", error.localizedDescription, error.userInfo, [NSThread callStackSymbols]);
    }

    return didSave;
}

- (BOOL)saveContext:(NSManagedObjectContext *)context error:(NSError **)errorPtr {
    __block BOOL hasChanges = NO;

    [context performBlockAndWait:^{
        hasChanges = [context hasChanges];
    }];

    if (!hasChanges) return YES;

    __block NSError *error = nil;
    __block BOOL didSave = NO;

    [context performBlockAndWait:^{
        didSave = [context save:&error];
    }];

    if (!didSave && error && errorPtr) {
        *errorPtr = error;
    }

    return didSave;
}

@end

NSFetchedResultsController

- (NSFetchedResultsController *)setupFetchedResultsController {
    if (!_fetchedResultsController) {

        NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:[TTInboxItem entityName]];
        fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:TTInboxItemAttributes.sortDate ascending:NO]];

        NSPredicate *itemPredicate = [NSPredicate predicateWithFormat:@"%K == %@", TTInboxItemAttributes.type, [TTRelationship entityName]];
        NSPredicate *notArchivedPredicate = [NSPredicate predicateWithFormat:@"%K.%K != %@", TTInboxItemRelationships.relationship, TTRelationshipAttributes.archived, @YES];
        NSPredicate *notArchivedPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[itemPredicate, notArchivedPredicate]];

        fetchRequest.predicate = notArchivedPredicate;

        self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                                                            managedObjectContext:self.persistenceManager.managedObjectContext
                                                                              sectionNameKeyPath:nil
                                                                                       cacheName:nil];

        self.fetchedResultsController.delegate = self.tableViewBatchUpdater;
        [self fetchInboxItems];
    }

    return _fetchedResultsController;
}

2 个答案:

答案 0 :(得分:1)

我以前的某个应用程序遇到了类似的问题。文档中的这一提示帮助我找到了解决方案。

来自Apple:

  

如果上下文的父存储是持久存储协调器,则更改将提交到外部存储。如果上下文的父存储是另一个受管对象上下文,则save:仅更新该父存储中的受管对象。要提交对外部存储的更改,必须将更改保存在上下文链中,包括父级是持久性存储协调器的上下文。

由于您拥有此结构MainQueueMOC -> PrivateBackgroundMOC -> PersistentStoreCoordinator,您应该尝试抓住您的上下文并执行[TTPersistenceManager.managedObjectContext save]之类的操作。如果您有多个,可以制作一个调用save和上下文的方法。

关于"我的假设是我们只是在进入后台时保存连接到持久性商店的MOC?"您可以尽可能多地保存,但总是检查您是否有更改,您可以在applicationDidEnterBackground:上执行此类操作,这样您就不会浪费时间。我确实在我的应用程序上手动保存更改,因为我确切知道何时需要更改。我依靠上面的代码进行背景保存。

来自Apple:

  

在调用save:方法之前,始终验证上下文是否有未提交的更改(使用hasChanges属性)。否则,核心数据可能会执行不必​​要的工作。

if (TTPersistenceManager.managedObjectContext.hasChanges) {
    [TTPersistenceManager.managedObjectContext save]
}

为什么在谓词中甚至不使用的项目中添加属性?

我认为这与获取请求有关,而不是持久性存储。我相信这与你之前的陈述有关。 "关系对象的archived属性设置为YES,但fetch仍然返回它。"我想一旦你解决了持久性问题,你的fetchRequest就不应该返回该项目。

答案 1 :(得分:0)

我还没看过这篇文章,但我还没有读完你的所有代码,但是MOC会缓存数据,甚至提取也不会强迫它再次进入数据库。那么请听NSManagedObjectContextDidSaveNotification通知并手动合并更改? ..主要是因为你需要告诉获取结果控制器(!),因为它还有一个缓存