2
0

Some improvement to observing user changes.

This commit is contained in:
Maarten Billemont 2017-04-29 15:01:24 -04:00
parent ea5be8efcb
commit fcaa5d1d8c
4 changed files with 78 additions and 57 deletions

View File

@ -35,6 +35,7 @@ typedef NS_ENUM( NSUInteger, MPImportResult ) {
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *mainContext))mocBlock; + (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *mainContext))mocBlock;
+ (BOOL)managedObjectContextPerformBlock:(void ( ^ )(NSManagedObjectContext *context))mocBlock; + (BOOL)managedObjectContextPerformBlock:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
+ (BOOL)managedObjectContextPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *context))mocBlock; + (BOOL)managedObjectContextPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
+ (id)managedObjectContextChanged:(void ( ^ )(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects))changedBlock;
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context; - (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
- (void)deleteAndResetStore; - (void)deleteAndResetStore;

View File

@ -131,6 +131,25 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
return YES; return YES;
} }
+ (id)managedObjectContextChanged:(void ( ^ )(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects))changedBlock {
NSManagedObjectContext *privateManagedObjectContextIfReady = [[self get] privateManagedObjectContextIfReady];
if (!privateManagedObjectContextIfReady)
return nil;
return PearlAddNotificationObserver( NSManagedObjectContextObjectsDidChangeNotification, privateManagedObjectContextIfReady, nil,
^(id host, NSNotification *note) {
NSMutableDictionary *affectedObjects = [NSMutableDictionary new];
for (NSManagedObject *object in note.userInfo[NSInsertedObjectsKey])
affectedObjects[object.objectID] = NSInsertedObjectsKey;
for (NSManagedObject *object in note.userInfo[NSUpdatedObjectsKey])
affectedObjects[object.objectID] = NSUpdatedObjectsKey;
for (NSManagedObject *object in note.userInfo[NSDeletedObjectsKey])
affectedObjects[object.objectID] = NSDeletedObjectsKey;
changedBlock( affectedObjects );
} );
}
- (NSManagedObjectContext *)mainManagedObjectContextIfReady { - (NSManagedObjectContext *)mainManagedObjectContextIfReady {
[self loadStore]; [self loadStore];
@ -196,19 +215,21 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext; self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
if ([self.mainManagedObjectContext respondsToSelector:@selector( automaticallyMergesChangesFromParent )]) // iOS 10+
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext. self.mainManagedObjectContext.automaticallyMergesChangesFromParent = YES;
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification, else
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainManagedObjectContext, NSNotification *note) { // When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
[mainManagedObjectContext performBlock:^{ PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
@try { self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainContext, NSNotification *note) {
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note]; [mainContext performBlock:^{
} @try {
@catch (NSException *exception) { [mainContext mergeChangesFromContextDidSaveNotification:note];
err( @"While merging changes:\n%@", [exception fullDescription] ); }
} @catch (NSException *exception) {
}]; err( @"While merging changes:\n%@", [exception fullDescription] );
} ); }
}];
} );
// Create a new store coordinator. // Create a new store coordinator.

View File

@ -16,7 +16,7 @@
- (BOOL)saveToStore { - (BOOL)saveToStore {
__block BOOL success = YES; __block BOOL success = YES;
if ([self hasChanges]) { if ([self hasChanges])
[self performBlockAndWait:^{ [self performBlockAndWait:^{
@try { @try {
NSError *error = nil; NSError *error = nil;
@ -28,7 +28,6 @@
err( @"While saving: %@", [exception fullDescription] ); err( @"While saving: %@", [exception fullDescription] );
} }
}]; }];
}
return success && (!self.parentContext || [self.parentContext saveToStore]); return success && (!self.parentContext || [self.parentContext saveToStore]);
} }

View File

@ -58,6 +58,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
@implementation MPUsersViewController { @implementation MPUsersViewController {
NSString *_masterPasswordChoice; NSString *_masterPasswordChoice;
NSOperationQueue *_afterUpdates; NSOperationQueue *_afterUpdates;
__weak id _contextChangedObserver;
} }
- (void)viewDidLoad { - (void)viewDidLoad {
@ -90,15 +91,6 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
self.userSelectionContainer.visible = NO; self.userSelectionContainer.visible = NO;
} }
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
PearlRemoveNotificationObservers();
[self.marqueeTipTimer invalidate];
}
- (void)viewDidAppear:(BOOL)animated { - (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated]; [super viewDidAppear:animated];
@ -130,6 +122,15 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
((MPWebViewController *)segue.destinationViewController).initialURL = [NSURL URLWithString:@"http://thanks.lhunath.com"]; ((MPWebViewController *)segue.destinationViewController).initialURL = [NSURL URLWithString:@"http://thanks.lhunath.com"];
} }
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self removeObservers];
[self.marqueeTipTimer invalidate];
}
#pragma mark - UITextFieldDelegate #pragma mark - UITextFieldDelegate
- (void)textFieldDidEndEditing:(UITextField *)textField { - (void)textFieldDidEndEditing:(UITextField *)textField {
@ -465,25 +466,23 @@ referenceSizeForFooterInSection:(NSInteger)section {
- (void)deleteUser:(NSManagedObjectID *)userID { - (void)deleteUser:(NSManagedObjectID *)userID {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity MPUserEntity *user = [MPUserEntity existingObjectWithID:userID inContext:context];
*user_ = [MPUserEntity existingObjectWithID:userID inContext:context]; if (!user)
if (!user_)
return; return;
[context deleteObject:user_]; [context deleteObject:user];
[context saveToStore]; [context saveToStore];
[self reloadUsers]; // I do NOT understand why our ObjectsDidChangeNotification isn't firing on saveToStore.
}]; }];
} }
- (void)resetUser:(NSManagedObjectID *)userID avatar:(MPAvatarCell *)avatarCell { - (void)resetUser:(NSManagedObjectID *)userID avatar:(MPAvatarCell *)avatarCell {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context]; MPUserEntity *user = [MPUserEntity existingObjectWithID:userID inContext:context];
if (!user_) if (!user)
return; return;
[[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{ [[MPiOSAppDelegate get] changeMasterPasswordFor:user saveInContext:context didResetBlock:^{
PearlMainQueue( ^{ PearlMainQueue( ^{
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell]; NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell];
[self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO [self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO
@ -645,15 +644,21 @@ referenceSizeForFooterInSection:(NSInteger)section {
}]; }];
} }
- (void)registerObservers { - (void)removeObservers {
[self removeKeyPathObservers]; [self removeKeyPathObservers];
PearlRemoveNotificationObservers();
[[NSNotificationCenter defaultCenter] removeObserver:_contextChangedObserver];
}
- (void)registerObservers {
[self removeObservers];
[self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock: [self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock:
^(id from, id to, NSKeyValueChange cause, MPUsersViewController *_self) { ^(id from, id to, NSKeyValueChange cause, MPUsersViewController *_self) {
[_self updateAvatarVisibility]; [_self updateAvatarVisibility];
}]; }];
PearlRemoveNotificationObservers();
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue], PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
^(MPUsersViewController *self, NSNotification *note) { ^(MPUsersViewController *self, NSNotification *note) {
self.userSelectionContainer.visible = NO; self.userSelectionContainer.visible = NO;
@ -675,36 +680,31 @@ referenceSizeForFooterInSection:(NSInteger)section {
[self.keyboardHeightConstraint updateConstant:keyboardHeight]; [self.keyboardHeightConstraint updateConstant:keyboardHeight];
} ); } );
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady]; if ((_contextChangedObserver = [MPiOSAppDelegate managedObjectContextChanged:^(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects) {
[UIView animateWithDuration:0.3f animations:^{ if ([[[affectedObjects allKeys] filteredArrayUsingPredicate:
self.avatarCollectionView.visible = mainContext != nil; [NSPredicate predicateWithBlock:^BOOL(NSManagedObjectID *objectID, NSDictionary *bindings) {
}]; return [objectID.entity.name isEqualToString:NSStringFromClass( [MPUserEntity class] )];
if (mainContext && self.storeLoadingActivity.isAnimating) }]] count])
[self.storeLoadingActivity stopAnimating]; [self reloadUsers];
if (!mainContext && !self.storeLoadingActivity.isAnimating) }]))
[self.storeLoadingActivity startAnimating]; [UIView animateWithDuration:0.3f animations:^{
self.avatarCollectionView.visible = YES;
[self.storeLoadingActivity stopAnimating];
}];
else
[UIView animateWithDuration:0.3f animations:^{
self.avatarCollectionView.visible = NO;
[self.storeLoadingActivity startAnimating];
}];
if (mainContext)
PearlAddNotificationObserver( NSManagedObjectContextObjectsDidChangeNotification, mainContext, nil,
^(MPUsersViewController *self, NSNotification *note) {
NSSet *insertedObjects = note.userInfo[NSInsertedObjectsKey];
NSSet *deletedObjects = note.userInfo[NSDeletedObjectsKey];
if ([[NSSetUnion( insertedObjects, deletedObjects )
filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
return [evaluatedObject isKindOfClass:[MPUserEntity class]];
}]] count])
[self reloadUsers];
} );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, [MPiOSAppDelegate get].storeCoordinator, nil, PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, [MPiOSAppDelegate get].storeCoordinator, nil,
^(MPUsersViewController *self, NSNotification *note) { ^(MPUsersViewController *self, NSNotification *note) {
self.userIDs = nil; self.userIDs = nil;
} ); } );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, [MPiOSAppDelegate get].storeCoordinator, nil, PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, [MPiOSAppDelegate get].storeCoordinator, nil,
^(MPUsersViewController *self, NSNotification *note) { ^(MPUsersViewController *self, NSNotification *note) {
PearlMainQueue( ^{ [self registerObservers];
[self registerObservers]; [self reloadUsers];
[self reloadUsers];
} );
} ); } );
} }