RoomVC: BF: Read receipts processing dramatically slows down UI

#1899

Build, cache and update read receipts on the processing queue.
This commit is contained in:
manuroe 2018-06-25 13:29:32 +02:00
parent 429ebe3ce0
commit 715d9da536
4 changed files with 110 additions and 61 deletions

View file

@ -6,6 +6,7 @@ Improvements:
* RoomVC: Add a re-request keys button on message unable to decrypt (#1879).
Bug fix:
* RoomVC: Read receipts processing dramatically slows down UI (#1899).
Changes in 0.6.17 (2018-06-01)
===============================================

View file

@ -34,10 +34,6 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag)
*/
@property(nonatomic) BOOL containsLastMessage;
/**
A Boolean value that determines whether some read receipts are currently displayed in this bubble.
*/
@property(nonatomic) BOOL hasReadReceipts;
/**
The event id of the current selected event inside the bubble. Default is nil.

View file

@ -48,13 +48,9 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil;
// Increase maximum number of components
self.maxComponentCount = 20;
// Initialize receipts flag
_hasReadReceipts = NO;
// Force the update of the text message to take into account the potential read receipts in the bubble display.
// Note: we don't update this attributed string here because the RoomBubbleCellData instances are created on a processing
// thread different from the UI thread.
self.attributedTextMessage = nil;
// Initialize read receipts
self.readReceipts = [NSMutableDictionary dictionary];
self.readReceipts[event.eventId] = [roomDataSource.room getEventReceipts:event.eventId sorted:YES];
}
return self;
@ -79,7 +75,6 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil;
[self refreshBubbleComponentsPosition];
}
shouldUpdateComponentsPosition = NO;
}
}
@ -163,9 +158,6 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil;
NSMutableAttributedString *currentAttributedTextMsg;
// Refresh the receipt flag during this process
_hasReadReceipts = NO;
NSInteger selectedComponentIndex = self.selectedComponentIndex;
NSInteger lastMessageIndex = self.containsLastMessage ? self.mostRecentComponentIndex : NSNotFound;
@ -203,11 +195,10 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil;
// Init attributed string with the first text component
currentAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:componentString];
}
// Vertical whitespace is added in case of read receipts
if ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO])
if (self.readReceipts[component.event.eventId].count)
{
_hasReadReceipts = YES;
// Add vertical whitespace in case of read receipts
[currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]];
}
@ -246,10 +237,9 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil;
// Append attributed text
[currentAttributedTextMsg appendAttributedString:componentString];
// Add vertical whitespace in case of read receipts
if ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO])
if (self.readReceipts[component.event.eventId].count)
{
_hasReadReceipts = YES;
// Add vertical whitespace in case of read receipts
[currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]];
}
}
@ -262,9 +252,6 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil;
{
// CAUTION: This method must be called on the main thread.
// Refresh the receipt flag during this process.
_hasReadReceipts = NO;
@synchronized(bubbleComponents)
{
// Check whether there is at least one component.
@ -283,7 +270,6 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil;
if (component.attributedTextMessage)
{
_hasReadReceipts = ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO] != nil);
break;
}
}
@ -308,7 +294,7 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil;
}
// Vertical whitespace is added in case of read receipts
if (_hasReadReceipts)
if (self.readReceipts.count)
{
[attributedString appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]];
}
@ -348,9 +334,8 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil;
component.position = CGPointMake(0, positionY);
// Add vertical whitespace in case of read receipts.
if ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO])
if (self.readReceipts[component.event.eventId])
{
_hasReadReceipts = YES;
[attributedString appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]];
}
@ -379,19 +364,6 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil;
}
}
- (void)setHasReadReceipts:(BOOL)hasReadReceipts
{
// Check whether there is something to do
if (_hasReadReceipts || hasReadReceipts)
{
// Update flag
_hasReadReceipts = hasReadReceipts;
// Recompute the text message layout
self.attributedTextMessage = nil;
}
}
- (void)setSelectedEventId:(NSString *)selectedEventId
{
// Check whether there is something to do
@ -517,6 +489,9 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil;
return NO;
}
// Update read receipts for this bubble
self.readReceipts[event.eventId] = [roomDataSource.room getEventReceipts:event.eventId sorted:YES];
return [super addEvent:event andRoomState:roomState];
}

View file

@ -96,24 +96,101 @@
- (void)didReceiveReceiptEvent:(MXEvent *)receiptEvent roomState:(MXRoomState *)roomState
{
// Override this callback to force rendering of each cell with read receipts information.
@synchronized(bubbles)
{
for (RoomBubbleCellData *cellData in bubbles)
// Do the processing on the same processing queue as MXKRoomDataSource
dispatch_async(MXKRoomDataSource.processingQueue, ^{
// Remove the previous displayed read receipt for each user who sent a
// new read receipt.
// To implement it, we need to find the sender id of each new read receipt
// among the read receipts array of all events in all bubbles.
NSMutableArray *readReceiptSenders = [receiptEvent.readReceiptSenders mutableCopy];
@synchronized(bubbles)
{
cellData.hasReadReceipts = NO;
NSMutableDictionary<NSString* /* eventId */, NSMutableArray<MXReceiptData*> *> *updatedCellDataReadReceipts = [NSMutableDictionary dictionary];
for (RoomBubbleCellData *cellData in bubbles)
{
for (NSString *eventId in cellData.readReceipts)
{
for (MXReceiptData *receiptData in cellData.readReceipts[eventId])
{
NSMutableArray *foundSenders = [NSMutableArray array];
for (NSString *senderId in readReceiptSenders)
{
if ([receiptData.userId isEqualToString:senderId])
{
// We find an existing displayed receipt, remove it
[foundSenders addObject:senderId];
if (!updatedCellDataReadReceipts[eventId])
{
updatedCellDataReadReceipts[eventId] = [cellData.readReceipts[eventId] mutableCopy];
}
[updatedCellDataReadReceipts[eventId] removeObject:receiptData];
break;
}
}
// As there is one (the last) read receipt displayed per user,
// we do not need to search for other read receipts of found users.
[readReceiptSenders removeObjectsInArray:foundSenders];
if (!readReceiptSenders.count)
{
// All senders have been found
break;
}
}
}
// Flush found changed to the cell data
for (NSString *eventId in updatedCellDataReadReceipts)
{
if (updatedCellDataReadReceipts[eventId].count)
{
cellData.readReceipts[eventId] = updatedCellDataReadReceipts[eventId];
}
else
{
cellData.readReceipts[eventId] = nil;
}
}
if (!readReceiptSenders.count)
{
// All senders have been found
break;
}
}
}
}
NSArray *readEventIds = receiptEvent.readReceiptEventIds;
for (NSString* eventId in readEventIds)
{
RoomBubbleCellData *cellData = [self cellDataOfEventWithEventId:eventId];
// Ignore the read receipts on the events without an actual display.
cellData.hasReadReceipts = !cellData.hasNoDisplay;
}
[super didReceiveReceiptEvent:receiptEvent roomState:roomState];
// Update cell data we have received a read receipt for
NSArray *readEventIds = receiptEvent.readReceiptEventIds;
for (NSString* eventId in readEventIds)
{
RoomBubbleCellData *cellData = [self cellDataOfEventWithEventId:eventId];
if (cellData)
{
@synchronized(bubbles)
{
if (!cellData.hasNoDisplay)
{
cellData.readReceipts[eventId] = [self.room getEventReceipts:eventId sorted:YES];
}
else
{
// Ignore the read receipts on the events without an actual display.
cellData.readReceipts[eventId] = nil;
}
}
}
}
dispatch_async(dispatch_get_main_queue(), ^{
// TODO: Be smarter and update only updated cells
[super didReceiveReceiptEvent:receiptEvent roomState:roomState];
});
});
}
#pragma mark -
@ -181,7 +258,7 @@
// Handle read receipts and read marker display.
// Ignore the read receipts on the bubble without actual display.
// Ignore the read receipts on collapsed bubbles
if ((self.showBubbleReceipts && cellData.hasReadReceipts && !isCollapsableCellCollapsed) || self.showReadMarker)
if ((self.showBubbleReceipts && cellData.readReceipts.count && !isCollapsableCellCollapsed) || self.showReadMarker)
{
// Read receipts container are inserted here on the right side into the content view.
// Some vertical whitespaces are added in message text view (see RoomBubbleCellData class) to insert correctly multiple receipts.
@ -194,10 +271,10 @@
if (component.event.sentState != MXEventSentStateFailed)
{
// Handle read receipts (if any)
if (self.showBubbleReceipts && cellData.hasReadReceipts && !isCollapsableCellCollapsed)
if (self.showBubbleReceipts && cellData.readReceipts.count && !isCollapsableCellCollapsed)
{
// Get the events receipts by ignoring the current user receipt.
NSArray* receipts = [self.room getEventReceipts:component.event.eventId sorted:YES];
NSArray* receipts = cellData.readReceipts[component.event.eventId];
NSMutableArray *roomMembers;
NSMutableArray *placeholders;