2
0

Load store synchronously to not return racy or invalid contexts.

This commit is contained in:
Maarten Billemont 2020-09-03 13:57:16 -04:00
parent e58b9ef34f
commit f665aeccc4

View File

@ -51,7 +51,6 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady { + (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady {
NSAssert( [[NSThread currentThread] isMainThread], @"Can only access main MOC from the main thread." );
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady]; NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
if (!mainManagedObjectContext || ![[NSThread currentThread] isMainThread]) if (!mainManagedObjectContext || ![[NSThread currentThread] isMainThread])
return nil; return nil;
@ -155,7 +154,42 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
- (NSManagedObjectContext *)mainManagedObjectContextIfReady { - (NSManagedObjectContext *)mainManagedObjectContextIfReady {
NSAssert( [[NSThread currentThread] isMainThread], @"Can only access main MOC from the main thread." );
[self loadStore]; [self loadStore];
if (!self.mainManagedObjectContext && self.privateManagedObjectContext.persistentStoreCoordinator) {
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
if (@available( iOS 10.0, macOS 10.12, * ))
self.mainManagedObjectContext.automaticallyMergesChangesFromParent = YES;
else
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainContext, NSNotification *note) {
[mainContext performBlock:^{
@try {
[mainContext mergeChangesFromContextDidSaveNotification:note];
}
@catch (NSException *exception) {
err( @"While merging changes:\n%@", [exception fullDescription] );
}
}];
} );
#if TARGET_OS_IPHONE
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, UIApp, [NSOperationQueue mainQueue],
^(MPAppDelegate_Shared *self, NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
} );
#else
PearlAddNotificationObserver( NSApplicationWillResignActiveNotification, NSApp, [NSOperationQueue mainQueue],
^(MPAppDelegate_Shared *self, NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
} );
#endif
}
return self.mainManagedObjectContext; return self.mainManagedObjectContext;
} }
@ -183,12 +217,12 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
} ); } );
// Do nothing if already fully set up, otherwise (re-)load the store. // Do nothing if already fully set up, otherwise (re-)load the store.
if (self.mainManagedObjectContext && self.privateManagedObjectContext) if ([self.storeCoordinator.persistentStores count])
return; return;
[self.storeQueue addOperationWithBlock:^{ NSOperation *storeOperation = [NSBlockOperation blockOperationWithBlock:^{
// Do nothing if already fully set up, otherwise (re-)load the store. // Do nothing if already fully set up, otherwise (re-)load the store.
if (self.mainManagedObjectContext && self.privateManagedObjectContext) if ([self.storeCoordinator.persistentStores count])
return; return;
// Unregister any existing observers and contexts. // Unregister any existing observers and contexts.
@ -201,12 +235,6 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
[self.privateManagedObjectContext reset]; [self.privateManagedObjectContext reset];
self.privateManagedObjectContext = nil; self.privateManagedObjectContext = nil;
}]; }];
NSError *error = nil;
for (NSPersistentStore *store in self.storeCoordinator.persistentStores)
if (![self.storeCoordinator removePersistentStore:store error:&error] || error) {
MPError( error, @"Couldn't remove persistence store from coordinator." );
return;
}
// Don't load when the store is corrupted. // Don't load when the store is corrupted.
if ([self.storeCorrupted boolValue]) if ([self.storeCorrupted boolValue])
@ -217,12 +245,10 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
// Create a new store coordinator. // Create a new store coordinator.
NSURL *localStoreURL = [self localStoreURL]; NSURL *localStoreURL = [self localStoreURL];
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent] if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent]
withIntermediateDirectories:YES attributes:nil error:&error]) { withIntermediateDirectories:YES attributes:nil error:&error]) {
MPError( error, @"Couldn't create our application support directory." ); MPError( error, @"Couldn't create our application support directory." );
PearlRemoveNotificationObserversFrom( self.mainManagedObjectContext );
self.mainManagedObjectContext = nil;
self.privateManagedObjectContext = nil;
return; return;
} }
if (![self.storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:localStoreURL if (![self.storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:localStoreURL
@ -232,9 +258,6 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
STORE_OPTIONS STORE_OPTIONS
} error:&error]) { } error:&error]) {
MPError( error, @"Failed to open store." ); MPError( error, @"Failed to open store." );
PearlRemoveNotificationObserversFrom( self.mainManagedObjectContext );
self.mainManagedObjectContext = nil;
self.privateManagedObjectContext = nil;
self.storeCorrupted = @YES; self.storeCorrupted = @YES;
[self handleCoordinatorError:error]; [self handleCoordinatorError:error];
return; return;
@ -246,42 +269,14 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
self.privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; self.privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
self.privateManagedObjectContext.persistentStoreCoordinator = self.storeCoordinator; self.privateManagedObjectContext.persistentStoreCoordinator = self.storeCoordinator;
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
if (@available( iOS 10.0, macOS 10.12, * ))
self.mainManagedObjectContext.automaticallyMergesChangesFromParent = YES;
else
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainContext, NSNotification *note) {
[mainContext performBlock:^{
@try {
[mainContext mergeChangesFromContextDidSaveNotification:note];
}
@catch (NSException *exception) {
err( @"While merging changes:\n%@", [exception fullDescription] );
}
}];
} );
#if TARGET_OS_IPHONE
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, UIApp, [NSOperationQueue mainQueue],
^(MPAppDelegate_Shared *self, NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
} );
#else
PearlAddNotificationObserver( NSApplicationWillResignActiveNotification, NSApp, [NSOperationQueue mainQueue],
^(MPAppDelegate_Shared *self, NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
} );
#endif
// Perform a data sanity check on the newly loaded store to find and fix any issues. // Perform a data sanity check on the newly loaded store to find and fix any issues.
if ([[MPConfig get].checkInconsistency boolValue]) if ([[MPConfig get].checkInconsistency boolValue])
[MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) { [MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
[self findAndFixInconsistenciesSaveInContext:context]; [self findAndFixInconsistenciesSaveInContext:context];
}]; }];
}]; }];
[self.storeQueue addOperations:@[ storeOperation ] waitUntilFinished:YES];
} }
- (void)retryCorruptStore { - (void)retryCorruptStore {