Merge branch 'charlesmchen/databaseObservation'

pull/1/head
Matthew Chen 8 years ago
commit 1486ef858e

@ -219,6 +219,10 @@ typedef enum : NSUInteger {
@property (nonatomic) UIView *scrollDownButton; @property (nonatomic) UIView *scrollDownButton;
@property (nonatomic) BOOL isViewVisible;
@property (nonatomic) BOOL isAppInBackground;
@property (nonatomic) BOOL shouldObserveDBModifications;
@end @end
#pragma mark - #pragma mark -
@ -294,6 +298,30 @@ typedef enum : NSUInteger {
selector:@selector(identityStateDidChange:) selector:@selector(identityStateDidChange:)
name:kNSNotificationName_IdentityStateDidChange name:kNSNotificationName_IdentityStateDidChange
object:nil]; object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didChangePreferredContentSize:)
name:UIContentSizeCategoryDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(yapDatabaseModified:)
name:YapDatabaseModifiedNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cancelReadTimer)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
} }
- (void)blockedPhoneNumbersDidChange:(id)notification - (void)blockedPhoneNumbersDidChange:(id)notification
@ -336,16 +364,16 @@ typedef enum : NSUInteger {
_composeOnOpen = keyboardOnViewAppearing; _composeOnOpen = keyboardOnViewAppearing;
_callOnOpen = callOnViewAppearing; _callOnOpen = callOnViewAppearing;
// We need to create the "unread indicator" before we mark
// all messages as read.
[self ensureDynamicInteractions];
[self.uiDatabaseConnection beginLongLivedReadTransaction]; [self.uiDatabaseConnection beginLongLivedReadTransaction];
self.messageMappings = self.messageMappings =
[[YapDatabaseViewMappings alloc] initWithGroups:@[ thread.uniqueId ] view:TSMessageDatabaseViewExtensionName]; [[YapDatabaseViewMappings alloc] initWithGroups:@[ thread.uniqueId ] view:TSMessageDatabaseViewExtensionName];
// We need to impose the range restrictions on the mappings immediately to avoid
// doing a great deal of unnecessary work and causing a perf hotspot.
[self updateMessageMappingRangeOptions];
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[self.messageMappings updateWithTransaction:transaction]; [self.messageMappings updateWithTransaction:transaction];
}]; }];
[self updateShouldObserveDBModifications];
self.page = 0; self.page = 0;
if (self.dynamicInteractions.unreadIndicatorPosition != nil) { if (self.dynamicInteractions.unreadIndicatorPosition != nil) {
@ -362,10 +390,6 @@ typedef enum : NSUInteger {
MIN(kYapDatabaseMaxInitialPageCount - 1, MIN(kYapDatabaseMaxInitialPageCount - 1,
(unreadIndicatorPosition + kPreferredSeenMessageCount) / kYapDatabasePageSize)); (unreadIndicatorPosition + kPreferredSeenMessageCount) / kYapDatabasePageSize));
} }
[self updateMessageMappingRangeOptions];
[self updateLoadEarlierVisible];
[self.collectionView reloadData];
} }
- (BOOL)userLeftGroup - (BOOL)userLeftGroup
@ -460,60 +484,16 @@ typedef enum : NSUInteger {
forCellWithReuseIdentifier:[OWSIncomingMessageCollectionViewCell mediaCellReuseIdentifier]]; forCellWithReuseIdentifier:[OWSIncomingMessageCollectionViewCell mediaCellReuseIdentifier]];
} }
- (void)toggleObservers:(BOOL)shouldObserve
{
if (shouldObserve) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didChangePreferredContentSize:)
name:UIContentSizeCategoryDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(yapDatabaseModified:)
name:YapDatabaseModifiedNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cancelReadTimer)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
} else {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIContentSizeCategoryDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:YapDatabaseModifiedNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
}
- (void)applicationWillEnterForeground:(NSNotification *)notification - (void)applicationWillEnterForeground:(NSNotification *)notification
{ {
[self resetContentAndLayout];
[self startReadTimer]; [self startReadTimer];
[self startExpirationTimerAnimations]; [self startExpirationTimerAnimations];
[self ensureDynamicInteractions]; self.isAppInBackground = NO;
} }
- (void)applicationDidEnterBackground:(NSNotification *)notification - (void)applicationDidEnterBackground:(NSNotification *)notification
{ {
self.isAppInBackground = YES;
if (self.hasClearedUnreadMessagesIndicator) { if (self.hasClearedUnreadMessagesIndicator) {
self.hasClearedUnreadMessagesIndicator = NO; self.hasClearedUnreadMessagesIndicator = NO;
[self.dynamicInteractions clearUnreadIndicatorState]; [self.dynamicInteractions clearUnreadIndicatorState];
@ -565,19 +545,7 @@ typedef enum : NSUInteger {
// or on another device. // or on another device.
[self hideInputIfNeeded]; [self hideInputIfNeeded];
self.messageAdapterCache = [[NSCache alloc] init]; self.isViewVisible = YES;
// We need to `beginLongLivedReadTransaction` before we update our
// mapping in order to jump to the most recent commit.
[self.uiDatabaseConnection beginLongLivedReadTransaction];
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[self.messageMappings updateWithTransaction:transaction];
}];
[self updateMessageMappingRangeOptions];
[self resetContentAndLayout];
[self toggleObservers:YES];
// restart any animations that were stopped e.g. while inspecting the contact info screens. // restart any animations that were stopped e.g. while inspecting the contact info screens.
[self startExpirationTimerAnimations]; [self startExpirationTimerAnimations];
@ -1009,7 +977,8 @@ typedef enum : NSUInteger {
DDLogDebug(@"%@ viewWillDisappear", self.tag); DDLogDebug(@"%@ viewWillDisappear", self.tag);
[super viewWillDisappear:animated]; [super viewWillDisappear:animated];
[self toggleObservers:NO];
self.isViewVisible = NO;
// Since we're using a custom back button, we have to do some extra work to manage the // Since we're using a custom back button, we have to do some extra work to manage the
// interactivePopGestureRecognizer // interactivePopGestureRecognizer
@ -1821,7 +1790,7 @@ typedef enum : NSUInteger {
- (void)didChangePreferredContentSize:(NSNotification *)notification - (void)didChangePreferredContentSize:(NSNotification *)notification
{ {
[self.collectionView.collectionViewLayout setMessageBubbleFont:[UIFont ows_dynamicTypeBodyFont]]; [self.collectionView.collectionViewLayout setMessageBubbleFont:[UIFont ows_dynamicTypeBodyFont]];
[self.collectionView reloadData]; [self resetContentAndLayout];
[self reloadInputToolbarSizeIfNeeded]; [self reloadInputToolbarSizeIfNeeded];
} }
@ -1889,8 +1858,8 @@ typedef enum : NSUInteger {
{ {
NSInteger rowCount = [self.collectionView numberOfItemsInSection:indexPath.section]; NSInteger rowCount = [self.collectionView numberOfItemsInSection:indexPath.section];
for (NSInteger row = indexPath.row + 1; row < rowCount; row++) { for (NSInteger row = indexPath.row + 1; row < rowCount; row++) {
id<OWSMessageData> nextMessage = NSIndexPath *nextIndexPath = [NSIndexPath indexPathForRow:row inSection:indexPath.section];
[self messageAtIndexPath:[NSIndexPath indexPathForRow:row inSection:indexPath.section]]; TSInteraction *nextMessage = [self interactionAtIndexPath:nextIndexPath];
if ([nextMessage isKindOfClass:[TSOutgoingMessage class]]) { if ([nextMessage isKindOfClass:[TSOutgoingMessage class]]) {
return (TSOutgoingMessage *)nextMessage; return (TSOutgoingMessage *)nextMessage;
} }
@ -2270,41 +2239,8 @@ typedef enum : NSUInteger {
self.page = MIN(self.page + 1, (NSUInteger)kYapDatabaseMaxPageCount - 1); self.page = MIN(self.page + 1, (NSUInteger)kYapDatabaseMaxPageCount - 1);
// To update a YapDatabaseViewMappings, you can call either: [self resetMappings];
//
// * [YapDatabaseViewMappings updateWithTransaction]
// * [YapDatabaseViewMappings getSectionChanges:rowChanges:forNotifications:withMappings:]
//
// ...but you can't call both.
//
// If ensureDynamicInteractionsForThread modifies the database,
// the mappings will be updated by yapDatabaseModified.
// This will leave the mapping range in a bad state.
// Therefore we temporarily disable observation of YapDatabaseModifiedNotification
// while updating the range and the dynamic interactions.
[[NSNotificationCenter defaultCenter] removeObserver:self name:YapDatabaseModifiedNotification object:nil];
// We need to update the dynamic interactions after loading earlier messages,
// since the unseen indicator may need to move or change.
[self ensureDynamicInteractions];
[self updateMessageMappingRangeOptions];
// We need to `beginLongLivedReadTransaction` before we update our
// mapping in order to jump to the most recent commit.
[self.uiDatabaseConnection beginLongLivedReadTransaction];
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[self.messageMappings updateWithTransaction:transaction];
}];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(yapDatabaseModified:)
name:YapDatabaseModifiedNotification
object:nil];
[self.collectionView.collectionViewLayout
invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
[self.collectionView reloadData];
[self.collectionView layoutSubviews]; [self.collectionView layoutSubviews];
self.collectionView.contentOffset = CGPointMake(0, self.collectionView.contentSize.height - scrollDistanceToBottom); self.collectionView.contentOffset = CGPointMake(0, self.collectionView.contentSize.height - scrollDistanceToBottom);
@ -3282,6 +3218,10 @@ typedef enum : NSUInteger {
// the database is modified. That doesn't seem optimal, but // the database is modified. That doesn't seem optimal, but
// in practice it's efficient enough. // in practice it's efficient enough.
if (!self.shouldObserveDBModifications) {
return;
}
// We need to `beginLongLivedReadTransaction` before we update our // We need to `beginLongLivedReadTransaction` before we update our
// models in order to jump to the most recent commit. // models in order to jump to the most recent commit.
NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction]; NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction];
@ -3330,7 +3270,6 @@ typedef enum : NSUInteger {
// range. // range.
[self updateMessageMappingRangeOptions]; [self updateMessageMappingRangeOptions];
[self resetContentAndLayout]; [self resetContentAndLayout];
return; return;
} }
@ -3388,9 +3327,7 @@ typedef enum : NSUInteger {
} }
completion:^(BOOL success) { completion:^(BOOL success) {
if (!success) { if (!success) {
[self.collectionView.collectionViewLayout [self resetContentAndLayout];
invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
[self.collectionView reloadData];
} }
[self updateLastVisibleTimestamp]; [self updateLastVisibleTimestamp];
@ -3439,6 +3376,8 @@ typedef enum : NSUInteger {
- (id<OWSMessageData>)messageAtIndexPath:(NSIndexPath *)indexPath - (id<OWSMessageData>)messageAtIndexPath:(NSIndexPath *)indexPath
{ {
OWSAssert(self.messageAdapterCache);
TSInteraction *interaction = [self interactionAtIndexPath:indexPath]; TSInteraction *interaction = [self interactionAtIndexPath:indexPath];
id<OWSMessageData> messageAdapter = [self.messageAdapterCache objectForKey:interaction.uniqueId]; id<OWSMessageData> messageAdapter = [self.messageAdapterCache objectForKey:interaction.uniqueId];
@ -4182,9 +4121,7 @@ typedef enum : NSUInteger {
[groupMemberIds addObject:[TSAccountManager localNumber]]; [groupMemberIds addObject:[TSAccountManager localNumber]];
groupModel.groupMemberIds = [NSMutableArray arrayWithArray:[groupMemberIds allObjects]]; groupModel.groupMemberIds = [NSMutableArray arrayWithArray:[groupMemberIds allObjects]];
[self updateGroupModelTo:groupModel successCompletion:nil]; [self updateGroupModelTo:groupModel successCompletion:nil];
[self.collectionView.collectionViewLayout [self resetContentAndLayout];
invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
[self.collectionView reloadData];
} }
- (void)popAllConversationSettingsViews - (void)popAllConversationSettingsViews
@ -4210,6 +4147,65 @@ typedef enum : NSUInteger {
return ![interaction isKindOfClass:[TSUnreadIndicatorInteraction class]]; return ![interaction isKindOfClass:[TSUnreadIndicatorInteraction class]];
} }
#pragma mark - Database Observation
- (void)setIsViewVisible:(BOOL)isViewVisible
{
_isViewVisible = isViewVisible;
[self updateShouldObserveDBModifications];
}
- (void)setIsAppInBackground:(BOOL)isAppInBackground
{
_isAppInBackground = isAppInBackground;
[self updateShouldObserveDBModifications];
}
- (void)updateShouldObserveDBModifications
{
self.shouldObserveDBModifications = self.isViewVisible && !self.isAppInBackground;
}
- (void)setShouldObserveDBModifications:(BOOL)shouldObserveDBModifications
{
if (_shouldObserveDBModifications == shouldObserveDBModifications) {
return;
}
_shouldObserveDBModifications = shouldObserveDBModifications;
if (self.shouldObserveDBModifications) {
[self resetMappings];
}
}
- (void)resetMappings
{
// If we're entering "active" mode (e.g. view is visible and app is in foreground),
// reset all state updated by yapDatabaseModified:.
if (self.messageMappings != nil) {
// Before we begin observing database modifications, make sure
// our mapping and table state is up-to-date.
//
// We need to `beginLongLivedReadTransaction` before we update our
// mapping in order to jump to the most recent commit.
[self.uiDatabaseConnection beginLongLivedReadTransaction];
[self updateMessageMappingRangeOptions];
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[self.messageMappings updateWithTransaction:transaction];
}];
}
self.messageAdapterCache = [[NSCache alloc] init];
[self resetContentAndLayout];
[self updateLoadEarlierVisible];
[self ensureDynamicInteractions];
[self updateBackButtonUnreadCount];
[self updateNavigationBarSubtitleLabel];
}
#pragma mark - Class methods #pragma mark - Class methods
+ (UINib *)nib + (UINib *)nib

@ -432,17 +432,15 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
return; return;
} }
DDLogDebug(@"%@ shouldObserveDBModifications: %d -> %d",
self.tag,
_shouldObserveDBModifications,
shouldObserveDBModifications);
_shouldObserveDBModifications = shouldObserveDBModifications; _shouldObserveDBModifications = shouldObserveDBModifications;
if (!self.shouldObserveDBModifications) { if (self.shouldObserveDBModifications) {
return; [self resetMappings];
} }
}
- (void)resetMappings
{
// If we're entering "active" mode (e.g. view is visible and app is in foreground), // If we're entering "active" mode (e.g. view is visible and app is in foreground),
// reset all state updated by yapDatabaseModified:. // reset all state updated by yapDatabaseModified:.
if (self.threadMappings != nil) { if (self.threadMappings != nil) {
@ -460,6 +458,12 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
[[self tableView] reloadData]; [[self tableView] reloadData];
[self checkIfEmptyView]; [self checkIfEmptyView];
[self updateInboxCountLabel]; [self updateInboxCountLabel];
// If the user hasn't already granted contact access
// we don't want to request until they receive a message.
if ([TSThread numberOfKeysInCollection] > 0) {
[self.contactsManager requestSystemContactsOnce];
}
} }
- (void)applicationWillEnterForeground:(NSNotification *)notification - (void)applicationWillEnterForeground:(NSNotification *)notification
@ -825,23 +829,27 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
_viewingThreadsIn = viewingThreadsIn; _viewingThreadsIn = viewingThreadsIn;
self.segmentedControl.selectedSegmentIndex = (viewingThreadsIn == kInboxState ? 0 : 1); self.segmentedControl.selectedSegmentIndex = (viewingThreadsIn == kInboxState ? 0 : 1);
if (didChange || !self.threadMappings) { if (didChange || !self.threadMappings) {
[self changeToGrouping:(viewingThreadsIn == kInboxState ? TSInboxGroup : TSArchiveGroup)]; [self updateMappings];
} else { } else {
[self checkIfEmptyView]; [self checkIfEmptyView];
[self updateReminderViews]; [self updateReminderViews];
} }
} }
- (void)changeToGrouping:(NSString *)grouping { - (NSString *)currentGrouping
OWSAssert([NSThread isMainThread]); {
return self.viewingThreadsIn == kInboxState ? TSInboxGroup : TSArchiveGroup;
}
self.shouldObserveDBModifications = NO; - (void)updateMappings
{
OWSAssert([NSThread isMainThread]);
self.threadMappings = self.threadMappings = [[YapDatabaseViewMappings alloc] initWithGroups:@[ self.currentGrouping ]
[[YapDatabaseViewMappings alloc] initWithGroups:@[ grouping ] view:TSThreadDatabaseViewExtensionName]; view:TSThreadDatabaseViewExtensionName];
[self.threadMappings setIsReversed:YES forGroup:grouping]; [self.threadMappings setIsReversed:YES forGroup:self.currentGrouping];
[self updateShouldObserveDBModifications]; [self resetMappings];
[[self tableView] reloadData]; [[self tableView] reloadData];
[self checkIfEmptyView]; [self checkIfEmptyView];
@ -861,9 +869,19 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
} }
- (void)yapDatabaseModified:(NSNotification *)notification { - (void)yapDatabaseModified:(NSNotification *)notification {
if (!self.shouldObserveDBModifications) {
return;
}
NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction]; NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction];
NSArray *sectionChanges = nil;
NSArray *rowChanges = nil; if (![[self.uiDatabaseConnection ext:TSThreadDatabaseViewExtensionName] hasChangesForGroup:self.currentGrouping
inNotifications:notifications]) {
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[self.self.threadMappings updateWithTransaction:transaction];
}];
return;
}
// If the user hasn't already granted contact access // If the user hasn't already granted contact access
// we don't want to request until they receive a message. // we don't want to request until they receive a message.
@ -871,10 +889,8 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
[self.contactsManager requestSystemContactsOnce]; [self.contactsManager requestSystemContactsOnce];
} }
if (!self.shouldObserveDBModifications) { NSArray *sectionChanges = nil;
return; NSArray *rowChanges = nil;
}
[[self.uiDatabaseConnection ext:TSThreadDatabaseViewExtensionName] getSectionChanges:&sectionChanges [[self.uiDatabaseConnection ext:TSThreadDatabaseViewExtensionName] getSectionChanges:&sectionChanges
rowChanges:&rowChanges rowChanges:&rowChanges
forNotifications:notifications forNotifications:notifications

Loading…
Cancel
Save