-
Notifications
You must be signed in to change notification settings - Fork 307
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
Paywalls
: improve image caching
#3498
Conversation
expect(self.urlSession.cachePolicy) == .returnCacheDataElseLoad | ||
} | ||
|
||
func testLoadingImageResetsPreviousResult() async throws { |
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.
This test is pretty complicated but it was the simplest way I could confirm that the image is reset before loading the new one, without any race conditions.
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.
makes sense. At least the code is very clear
1fd50ad
to
bffe57d
Compare
|
||
// Load images in a background thread | ||
return await Task<Value, Never> | ||
.detached(priority: .utility) { data.toImage() } |
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.
This is new
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.
Feels like utility
might be a bit low?
I'd imagined we'd have the pre-warming loader use utility
or background
and then userInitiated
default
when loading the paywall, given the potential impact on conversion for an image taking too long to load, and that there's probably not a lot more going on that's more important than the image at the time of displaying a paywall.
What do you think?
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.
The pre-warming is actually happening with .default
QOS because that's what OperationDispatcher.workerQueue
is set up with :/
We should talk about changing that, maybe having multiple queues.
But I agree, I'll change this to default
.
return nil | ||
} | ||
|
||
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { |
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.
This is slightly different than the original implementation to allow responses for local files.
.data(for: .init(url: url, cachePolicy: .returnCacheDataElseLoad)) | ||
|
||
do { | ||
try Task.checkCancellation() |
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.
This allows preventing the image from being decoded if the task is already cancelled.
#if os(macOS) | ||
if let image = NSImage(data: self) { | ||
return .success(.init(nsImage: image)) | ||
} else { | ||
return .failure(.invalidImage) | ||
} |
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.
We don't support macOS
yet, but this makes the class compile, and it'll be ready when we do.
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.
great catch, I keep forgetting macOS
RevenueCatUI/Views/RemoteImage.swift
Outdated
} | ||
} | ||
.transition(Self.transition) | ||
.task(id: self.url) { |
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.
This automatically cancels the task when the url changes.
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.
🤔 let's maybe add a comment? Feels like that would be easy to miss
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.
It's the canonical way of implementing task: making the ID whatever parameter the task depends on. But yeah I'll add a comment 👍🏻
c44985b
to
cd6b37b
Compare
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #3498 +/- ##
==========================================
- Coverage 86.05% 86.01% -0.04%
==========================================
Files 237 237
Lines 17226 17231 +5
==========================================
- Hits 14823 14821 -2
- Misses 2403 2410 +7 ☔ View full report in Codecov by Sentry. |
RevenueCatUI/Data/Strings.swift
Outdated
@@ -16,6 +16,7 @@ import RevenueCat | |||
|
|||
// swiftlint:disable identifier_name | |||
|
|||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) |
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.
I realize it's not going to make a difference in practice, but our entire SDK is technically 6.2+ on watchOS, might as well set that as the min deployment target. Actually, cocoapods might complain if we don't? Not sure
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.
and given that RCUI is 15+, maybe it'll complain further about that for all OS versions?
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.
Oops thanks, not sure where I got this.
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.
Oh this is because some method I needed from URLSession wasn't available until specifically these versions. So having a lower watchOS one doesn't matter, it's just overly available. But I'll update for consistency.
|
||
// Load images in a background thread | ||
return await Task<Value, Never> | ||
.detached(priority: .utility) { data.toImage() } |
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.
Feels like utility
might be a bit low?
I'd imagined we'd have the pre-warming loader use utility
or background
and then userInitiated
default
when loading the paywall, given the potential impact on conversion for an image taking too long to load, and that there's probably not a lot more going on that's more important than the image at the time of displaying a paywall.
What do you think?
#if os(macOS) | ||
if let image = NSImage(data: self) { | ||
return .success(.init(nsImage: image)) | ||
} else { | ||
return .failure(.invalidImage) | ||
} |
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.
great catch, I keep forgetting macOS
RevenueCatUI/Views/RemoteImage.swift
Outdated
} | ||
} | ||
.transition(Self.transition) | ||
.task(id: self.url) { |
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.
🤔 let's maybe add a comment? Feels like that would be easy to miss
expect(self.urlSession.cachePolicy) == .returnCacheDataElseLoad | ||
} | ||
|
||
func testLoadingImageResetsPreviousResult() async throws { |
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.
makes sense. At least the code is very clear
couple of small questions, looking great though |
Supersedes #3477. This replaces `AsyncImage` with a custom implementation that allows us to ensure caching works reliably by explicitly using a custom `URLSession` with a shared `URLCache`.
23e99f2
to
d1761f4
Compare
@aboedo all comments addressed. |
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!
**This is an automatic release.** ### RevenueCatUI * `Paywalls`: improve image caching (#3498) via NachoSoto (@NachoSoto) * `Paywalls`: change style of CTA button to be consistent with other platforms (#3507) via NachoSoto (@NachoSoto) * `Paywalls`: open footer links on Safari on Catalyst (#3508) via NachoSoto (@NachoSoto) * `Paywalls`: fix compilation on Xcode < 14.3 (#3513) via NachoSoto (@NachoSoto) * Fix typo in zh-Hans localization of RevenueCatUI (#3512) via Francis Feng (@francisfeng) * `Paywalls`: fix PaywallViewControllerDelegate access from Objective-C (#3510) via noncenz (@noncenz) * `Paywalls`: open Terms and Privacy Policy links in-app (#3475) via Andy Boedo (@aboedo) * `Paywalls`: fix empty description when using `custom` package type and `{{ sub_period }}` (#3495) via Andy Boedo (@aboedo) * `Paywalls`: use `HEIC` images (#3496) via NachoSoto (@NachoSoto) * Paywalls: disable the close button when an action is in progress (#3474) via Andy Boedo (@aboedo) * `Paywalls`: adjusted German translations (#3476) via Tensei (@tensei) * Paywalls: Improve Chinese localization (#3489) via Andy Boedo (@aboedo) ### Other Changes * `CircleCI`: add git credentials to snapshot generation (#3506) via NachoSoto (@NachoSoto) * `StoreProduct`: improve `priceFormatter` documentation (#3500) via NachoSoto (@NachoSoto) * `Paywalls`: fix tests (#3499) via NachoSoto (@NachoSoto) * `Optimization`: changed `first` to `first(where:)` (#3467) via NachoSoto (@NachoSoto)
Supersedes #3477.
This replaces
AsyncImage
with a custom implementation that allows us to ensure caching works reliably by explicitly using a customURLSession
with a sharedURLCache
.