diff --git a/platform-darwin/Source/MPAppDelegate_Store.h b/platform-darwin/Source/MPAppDelegate_Store.h index acc3662f..836f6b55 100644 --- a/platform-darwin/Source/MPAppDelegate_Store.h +++ b/platform-darwin/Source/MPAppDelegate_Store.h @@ -35,6 +35,7 @@ typedef NS_ENUM( NSUInteger, MPImportResult ) { + (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *mainContext))mocBlock; + (BOOL)managedObjectContextPerformBlock:(void ( ^ )(NSManagedObjectContext *context))mocBlock; + (BOOL)managedObjectContextPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *context))mocBlock; ++ (id)managedObjectContextChanged:(void ( ^ )(NSDictionary *affectedObjects))changedBlock; - (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context; - (void)deleteAndResetStore; diff --git a/platform-darwin/Source/MPAppDelegate_Store.m b/platform-darwin/Source/MPAppDelegate_Store.m index 66242070..878374bd 100644 --- a/platform-darwin/Source/MPAppDelegate_Store.m +++ b/platform-darwin/Source/MPAppDelegate_Store.m @@ -131,6 +131,25 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); return YES; } ++ (id)managedObjectContextChanged:(void ( ^ )(NSDictionary *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 { [self loadStore]; @@ -196,19 +215,21 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext; - - // When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext. - PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification, - self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainManagedObjectContext, NSNotification *note) { - [mainManagedObjectContext performBlock:^{ - @try { - [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note]; - } - @catch (NSException *exception) { - err( @"While merging changes:\n%@", [exception fullDescription] ); - } - }]; - } ); + if ([self.mainManagedObjectContext respondsToSelector:@selector( automaticallyMergesChangesFromParent )]) // iOS 10+ + 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] ); + } + }]; + } ); // Create a new store coordinator. diff --git a/platform-darwin/Source/MPEntities.m b/platform-darwin/Source/MPEntities.m index 74c3355b..f7157825 100644 --- a/platform-darwin/Source/MPEntities.m +++ b/platform-darwin/Source/MPEntities.m @@ -16,7 +16,7 @@ - (BOOL)saveToStore { __block BOOL success = YES; - if ([self hasChanges]) { + if ([self hasChanges]) [self performBlockAndWait:^{ @try { NSError *error = nil; @@ -28,7 +28,6 @@ err( @"While saving: %@", [exception fullDescription] ); } }]; - } return success && (!self.parentContext || [self.parentContext saveToStore]); } diff --git a/platform-darwin/Source/iOS/MPUsersViewController.m b/platform-darwin/Source/iOS/MPUsersViewController.m index 2c51f2f9..52cf53b4 100644 --- a/platform-darwin/Source/iOS/MPUsersViewController.m +++ b/platform-darwin/Source/iOS/MPUsersViewController.m @@ -58,6 +58,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) { @implementation MPUsersViewController { NSString *_masterPasswordChoice; NSOperationQueue *_afterUpdates; + __weak id _contextChangedObserver; } - (void)viewDidLoad { @@ -90,15 +91,6 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) { self.userSelectionContainer.visible = NO; } -- (void)viewWillDisappear:(BOOL)animated { - - [super viewWillDisappear:animated]; - - PearlRemoveNotificationObservers(); - - [self.marqueeTipTimer invalidate]; -} - - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; @@ -130,6 +122,15 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) { ((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 - (void)textFieldDidEndEditing:(UITextField *)textField { @@ -465,25 +466,23 @@ referenceSizeForFooterInSection:(NSInteger)section { - (void)deleteUser:(NSManagedObjectID *)userID { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - MPUserEntity - *user_ = [MPUserEntity existingObjectWithID:userID inContext:context]; - if (!user_) + MPUserEntity *user = [MPUserEntity existingObjectWithID:userID inContext:context]; + if (!user) return; - [context deleteObject:user_]; + [context deleteObject:user]; [context saveToStore]; - [self reloadUsers]; // I do NOT understand why our ObjectsDidChangeNotification isn't firing on saveToStore. }]; } - (void)resetUser:(NSManagedObjectID *)userID avatar:(MPAvatarCell *)avatarCell { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context]; - if (!user_) + MPUserEntity *user = [MPUserEntity existingObjectWithID:userID inContext:context]; + if (!user) return; - [[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{ + [[MPiOSAppDelegate get] changeMasterPasswordFor:user saveInContext:context didResetBlock:^{ PearlMainQueue( ^{ NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell]; [self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO @@ -645,15 +644,21 @@ referenceSizeForFooterInSection:(NSInteger)section { }]; } -- (void)registerObservers { +- (void)removeObservers { [self removeKeyPathObservers]; + PearlRemoveNotificationObservers(); + [[NSNotificationCenter defaultCenter] removeObserver:_contextChangedObserver]; +} + +- (void)registerObservers { + + [self removeObservers]; [self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock: ^(id from, id to, NSKeyValueChange cause, MPUsersViewController *_self) { [_self updateAvatarVisibility]; }]; - PearlRemoveNotificationObservers(); PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue], ^(MPUsersViewController *self, NSNotification *note) { self.userSelectionContainer.visible = NO; @@ -675,36 +680,31 @@ referenceSizeForFooterInSection:(NSInteger)section { [self.keyboardHeightConstraint updateConstant:keyboardHeight]; } ); - NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady]; - [UIView animateWithDuration:0.3f animations:^{ - self.avatarCollectionView.visible = mainContext != nil; - }]; - if (mainContext && self.storeLoadingActivity.isAnimating) - [self.storeLoadingActivity stopAnimating]; - if (!mainContext && !self.storeLoadingActivity.isAnimating) - [self.storeLoadingActivity startAnimating]; + if ((_contextChangedObserver = [MPiOSAppDelegate managedObjectContextChanged:^(NSDictionary *affectedObjects) { + if ([[[affectedObjects allKeys] filteredArrayUsingPredicate: + [NSPredicate predicateWithBlock:^BOOL(NSManagedObjectID *objectID, NSDictionary *bindings) { + return [objectID.entity.name isEqualToString:NSStringFromClass( [MPUserEntity class] )]; + }]] count]) + [self reloadUsers]; + }])) + [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, ^(MPUsersViewController *self, NSNotification *note) { self.userIDs = nil; } ); PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, [MPiOSAppDelegate get].storeCoordinator, nil, ^(MPUsersViewController *self, NSNotification *note) { - PearlMainQueue( ^{ - [self registerObservers]; - [self reloadUsers]; - } ); + [self registerObservers]; + [self reloadUsers]; } ); }