-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Store dependencies default values on first access #1510
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
Store dependencies default values on first access #1510
Conversation
Oops, I thought that |
@tgrapperon I've pushed a branch which is similar to how I've been doing it. It's probably got a bit more overhead than yours but I needed more control over the global defaults. main...iampatbrown:swift-composable-architecture:shared-dependencies |
@iampatbrown Nice! For some reason, I wanted to deport caching closer to |
Yeah that makes sense. I mainly needed global dependencies so that's why I took that approach. I wasn't suggesting you change your implementation :)
Yes. The cross dependencies existed before I started on the project though. It wouldn't be something I'd really need myself. |
Here is a variant where successive values are compared in |
Thanks for looking into this! @mbrandonw and I chatted about it today and we think the main thing missing would be to tie the storage to @iampatbrown's branch looks like a good step in that direction. How do you feel about refining the solution in kind? (Side-note to @iampatbrown: nice |
@stephencelis I didn't spot that I've updated the PR with a potential implementation in this direction. I went the one-dictionary route, but it can be three like Pat's implementation, with a shared lock or three. It also seems that one could reuse the Please let me know what you think. |
I've just benchmarked reusing |
@stephencelis I've slightly simplified the implementation and renamed a few things. I think that's good for me. Please let me know what you think. |
Hi @tgrapperon, I just pushed 2f4d842...93d3af9 with some changes that came from trying to write tests for the subtle logic in the PR and realizing things aren't quite what the seem.
I think now we are comfortable merging this. One thing to note is that by putting the cached reference value in This doesn't really affect TCA since every |
Pushed one last commit bf17531 to update docs and update places we were using the |
@tgrapperon Thinking we'll merge soon, but want to make sure you are OK with all the changes we made. |
No problem. Beside unaccounted edge effects, I tried to see if there were cases where one legitimately wouldn't want this behavior, but I haven't found anything credible. |
Alternatively, if someone encounters a problematic configuration, it seems possible to expand |
Hi @stephencelis and @tgrapperon, private enum CurrentDateDependencyKey: DependencyKey {
static var liveValue: Date {
return Date()
}
}
public extension DependencyValues {
var currentDate: Date {
get { self[CurrentDateDependencyKey.self] }
set { self[CurrentDateDependencyKey.self] = newValue }
}
} I expect that every time I inject the current date using the Dependency property wrapper, the current date is returned, not a cached one. @Dependency(\.currentDate) var date1: Date
// after some time
@Dependency(\.currentDate) var date2: Date I expect that date1 != date2 How would you achieve the result I'm expecting with the code above? Thanks! |
Hey @csanfilippo! I think that you would want your dependency to vend a @Dependency(\.date) var date1
@Dependency(\.date) var date2
date1() != date2()
date1() != date1()
// or
@Dependency(\.date.now) var now1
@Dependency(\.date.now) var now2
now1 != now2
now1 == now1 // (of course) But you can apply the idea of returning "generators of values" (as functions or "callable" structs) to general dependencies other than date. It works the same for |
Thanks @tgrapperon! |
@csanfilippo, I think this is already achievable, as you can override any dependency from a parent reducer using the |
This attempts to mitigate issues similar to #1508, where it is easy to assume that some dependency's default value is only set once whereas a new value is silently generated each time the dependency is accessed. To prevent this potentially surprising behavior, the default value is saved into some global storage on first access, and reused on subsequent accesses.
Because a dependency can be accessed from any concurrent context, this global storage is accessed under locking. The complete caching operation result on a relatively small overhead:
This 0.1µs increase shouldn't be noticeable in real-life applications.