-
Notifications
You must be signed in to change notification settings - Fork 99
Fix poor message search performance #680
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
loadingSearchResults = true | ||
messageSearchController?.search(query: query, completion: { [weak self] _ in | ||
messageSearchController?.search(text: searchText) { [weak self] _ in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replaced the search(query:)
with search(text:)
since it is the same thing but with some optimizations
extension ChatMessage { | ||
|
||
func makeChannelSelectionInfo(with chatClient: ChatClient) -> ChannelSelectionInfo? { | ||
if let channelId = cid, | ||
let channel = chatClient.channelController(for: channelId).channel { | ||
let searchResult = ChannelSelectionInfo( | ||
channel: channel, | ||
message: self | ||
) | ||
return searchResult | ||
} else { | ||
return nil | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the extension because it could be used in other places , and since the performance it not good, better to not make it easy to use.
SDK Size
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM ✅ Left 2 minor comments.
CHANGELOG.md
Outdated
@@ -4,6 +4,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). | |||
# Upcoming | |||
|
|||
### 🔄 Changed | |||
- Fix poor message search performance [#680](https://github.com/GetStream/stream-chat-swiftui/pull/680) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Improve message search performance" sounds better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Done
queue.async { | ||
let results: [ChannelSelectionInfo] = messageSearchController.messages.compactMap { message in | ||
guard let channelId = message.cid else { return nil } | ||
guard let channel = messageSearchController.client.channelController(for: channelId).channel else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we have to go through the message search controller here? We have the client injected in the VM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah yes you are correct 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done ✅
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With how DB observer work at the moment then this is a way to go.
🔗 Issue Links
Fixes IOS-574
🎯 Goal
Fixes the poor performance of Message Search.
🛠 Implementation
Problem
The root cause of the issue was in the
updateMessageSearchResults()
function. We were accessing thechannelController.channel
for every search result. The problem with this is that we callstartObservers()
for everychannel
property access, which performs a Core Data fetch and, in this case, on the main thread.We can see this from the time profiler:

Besides this, we were calling
updateMessageSearchResults()
twice, for every completion block performed in theloadNextMessages()
and also in thedidUpdateMessages()
delegate calls.First Solution
My first idea was to follow the UIKit approach. There, we use the
dataStore
to fetch the channel directly. However, this also does not work for SwiftUI. The reason is that for UIKit, thedataStore
is accessed when the cell is reused, whereas in SwiftUI, we map all the results at once, and so we access thedataStore.channel
for every result again, which means we convert the DTO to model on the main thread multiple times.Profile result:

So, to avoid converting the channel models every time, I implemented a cache that would reuse the existing channels instead of always doing the DTO -> Model conversion.
This worked and the performance was great, but it does require an additional cache, which can increase the memory and is dependent on the amount of different channels we have in the result. So the final solution (check next section) is probably the better one.
Final Solution
Eventually, I realised that I could simply use the original implementation but offload the search results mapping to a background thread. This worked flawlessly, and it has the less amount of changes and does not require any additional caching.
Result (No hangs, and Main thread not busy):

🧪 Testing
Perform a message search in the Channel List. For example, search for "Hey".
Known Issue: The channel name is not rendered for some search results. This is an existing issue and out of the scope of this PR.
🎨 Changes
ScreenRecording_12-05-2024.01-13-47_1.MP4
ScreenRecording_12-05-2024.00-49-49_1.MP4
☑️ Checklist