Updates `UserProfileFinder` to have
`userProfiles(for:,transaction:)` that looks up
many profiles by either UUID or phone number.
Refactors the existing
`userProfile(for:,transaction:)` to use the batch
implementation.
Also adds Refinery.refineNonnilKeys to simplify a
repeating pattern where we need to fetch values
from a database where keys are nonnil. This
provides better type safety versus using
`refine(condition:,then:,otherwise:)` because the
closure can get non-optional keys as input.
Adds tests for the new user profile lookup code.
This is the first meaty part of optimizing
fetching display names.
Some contacts fall back to phone numbers as their
display names. This PR fetches them in a single
SQL query via
`OWSContactsManager.phoneNumbers(for:, transaction)`.
To achieve this, this PR introduces
`GRDBSignalAccountFinder.signalAccounts(for:,transaction:)`
to fetch many accounts at once.
In order to fetch many accounts at once, we need
to be able to fetch many values from a
ModelReadCache at once. So this PR introduces
`ModelReadCache.readValues(for:,transaction:)` and
`ModelReadCache.getValues(for:,transaction:,returnNilOnCacheMiss:)`.
Existing methods that operate on a single value
were refactored to use the batch methods.
This PR adds tests for this functionality, which
necessitated changing the visibility of various
private symbols and also improving the fake
profile manager to make it more configurable.
There's also a tiny optimization for Refinery to
avoid calling a closure that has no work to do.
This helps elide do-nothing SQL queries that would
otherwise have been introduced.
PR 2982 optimized
ConversationViewController.createGroupMembershipCollisionBannerIfNecessary`
that fetches user profiles off the main thread and
then, when that completes, uses them on the main
thread to find duplicate display names.
The optimization doesn't work for large groups.
Because `UserProfileReadCache` is backed by an
LRUCache with a maximum capacity of 32, there is
effectively no cache and profiles get fetched
twice.
The main purpose of this commit is to fix this
optimization for large groups without expanding
memory usage unnecessarily. While increasing the
LRU cache's size to 1000 (or some other arbitrary
value) would fix the problem very simply, my gut
tells me that we're going to keep running into
this problem.
Rather than pick an arbitrary "large enough"
value, this commit increases the cache size to be
just large enough for its intended purpose and to
keep it that size for just long enough to do its
job.
This commit introduces the concept of a "lease" on
a larger cache size. The cache's size is defined
as the largest of all its leases plus its
"regular" size (the size it was initialized with).
For now only the user profile cache uses it.
We ship with 49 stickers and the previous cache size of 32 led to
thrasing every time a chat was opened.
Since sticker packs can be as large as 200 stickers and we hold three
pages in cache while the keyboard is open, increase the cache size for
the main app to 600. For the share extension, make it 64 to minimize
memory use while avoiding threashing.
The cost of this cache is just the sticker metadata (unique ID, emoji
description, etc.) so it won't make much difference in memory
utilization.