Ios NSFetchedResults在保存持久MOC后返回旧数据 上下文

Ios NSFetchedResults在保存持久MOC后返回旧数据 上下文,ios,core-data,Ios,Core Data,我有一个简单的核心数据堆栈:MainQueueMOC->PrivateBackgroundMOC->persistentstorecordinator,它由我的TTPersistenceManager管理,如下所示: typedef NS_ENUM(NSInteger, TTPersistenceType) { TTPersistenceTypeInMemory, TTPersistenceTypeSQLite }; @interface TTPersistenceManage

我有一个简单的核心数据堆栈:
MainQueueMOC->PrivateBackgroundMOC->persistentstorecordinator
,它由我的
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;
item.kind = "relationship" AND item.relationship.archived == NO
目前我们只使用内存存储。

它的灵感来自于此。因此,MainQueueMOC是唯一的真相来源,PrivateBackgroundMOC只用于在后台保存到存储中,并且从不公开。如果您阅读本文,您会注意到我添加了一个名为
persist
的方法,
save
persist
之间的区别是:

  • save
    ,使用
    performBlockAndWait
  • persist
    ,使用
    performBlock和wait
    保存主队列MOC,使用
    performBlock
我之所以这么做,主要是因为这两段:

通常,任何时候离开应用程序时,我们都希望在持久性控制器上调用save。这保证了如果我们在暂停时被杀死,我们不会丢失数据

在大多数情况下,这是在主上下文和私有上下文中唯一需要调用save的地方

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

除以下问题外,此操作正常

问题 我们有一个
NSFetchedResultsController
,其谓词如下:

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;
item.kind = "relationship" AND item.relationship.archived == NO
我们不显示与存档关系关联的项目。用户可以滑动该行对其进行存档,这会更改
关系。存档=@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
关系
对象的
存档
属性设置为
,但提取仍然返回它

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

\一,。将
updatedAt
属性添加到
项目
,我们在每次更新
关系时设置当前日期

 relationship.item.updatedAt = [NSDate date];
 relationship.archived = @YES;
 [self.persistenceManager save];
\二,。始终调用
persist
而不是
save

relationship.archived = @YES;
[self.persistenceManager persist];
正确的方法是什么

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

为什么向
添加一个谓词中甚至没有使用的属性会起作用


更新:源代码

@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;
}

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

来自苹果:

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

由于您具有此结构
MainQueueMOC->PrivateBackgroundMOC->persistentstorecordinator
,因此您应该尝试获取上下文并执行类似于
[TTPersistenceManager.managedObjectContext save]
的操作。如果有多个上下文,可以创建一个调用save和上下文的方法

至于“我的假设是,当进入后台时,我们只保存连接到持久存储的MOC吗?”您可以保存您想要的内容,但始终要检查您是否有更改,您可以在
应用程序中执行类似操作。Identinterbackground:
,这样您就不会浪费时间。我确实会在我的应用程序上手动保存更改,因为我知道何时需要更改。我依靠上面的代码进行后台保存

来自苹果:

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

为什么向谓词中甚至没有使用的项添加属性会起作用


我认为这更多地与获取请求有关,而不是持久存储。我相信这与你先前的说法有关。“relationship对象的存档属性设置为YES,但fetch仍会返回该属性。”我认为,一旦您解决了持久化问题,fetchRequest就不会返回该项。

我没有读过这篇文章,也没有读过您的所有代码,但MOCs缓存数据,甚至fetch也不会迫使它再次进入数据库。因此,请收听NSManagedObjectContextDidSaveNotification通知并手动合并更改<强>。。主要是因为您需要告诉fetch results controller(!),因为它也有一个缓存

您可以发布堆栈创建和fetch results controller创建代码吗?@MarcusS.Zarra done,为
TTPersistenceManager
nsfetchedresultscocontroller
添加了源代码,您是否看到
NSFetchedResultsController
fire的委托方法?您针对的是哪个版本的iOS?仅供参考,将notArchived放在复合谓词的第一位。您将获得更好的性能。您是否在每次刷卡后调用
-performFetch:
@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