swift-composable-architecture
swift-composable-architecture copied to clipboard
Ignore actions sent to "IfLetStore" when state is "nil"
While exploring some TCA navigation stuff we have found that there are times that SwiftUI writes to bindings even when the view has been removed. This is problematic for our reducers that try to enforce the invariant that actions cannot be sent when state is nil.
We think the best way to handle this for now is to silently ignore actions sent to IfLetStore's when its state is nil. We accomplish this by introducing some internal functionality to Store.scope that allows tweaking the send for derived stores.
Importantly this does not include effects. So if a feature emits a long living effect, then the view goes away, and then the effect emits, that will still catch in a breakpoint.
This should (hopefully) be a relatively safe change to make, though feedback is more than welcome, as are alternate ideas. There are only a couple times we expect actions to be received by IfLetStore when its state is nil:
- When SwiftUI writes
falseornilto a presentation binding after the reducer's alreadyniled out the associated state. I think this only happen whennil-ing out multiple layers of nested, optional state. - When a library user tries to send an action in
onDisappear. This is technically OK to do with the (big) caveat that the domain will never be able to handleonDisappearitself.
We have a couple of these in our app so in general I think this would be a welcome change 👍
Somewhat related, though not strictly about bindings but effects in general -- I'm curious what design considerations led to the current "zombie effects" behaviour and WHY are they kept alive if state is nil?
It surprised me when I found out that if state is nil the associated effects are still running. What I expected instead was an ARC-like behaviour: if there's no reference memory is released => when state is nilled all respective effects are cancelled.
What about inverting the current behavior? so say someone wants their effects to run even if state is nil they can override a keepAliveIfStateNil param of e.g. pullback, otherwise, default to cancelling all effects when state is nilled.
side-note
As far as I understand the reason to keep the current breakpoint behavior it is to encourage a more conscientious use of effects. But this often comes at a cost of development experience, where we get random breakpoints like these that we're not quite sure what to do with.
Lately I've been using to solutions to this: • disabling almost all optional reducer breakpoint • replacing the binding with a variant catching erroneously sent actions thought this might break in the next iOS release.
I guess as long as those unexpected actions are received, your proposed solution is a good one as it's too tempting to disable all breakpoints.
While I agree with @ferologics on the appeal of auto cancellation on pop/dismiss (https://github.com/pointfreeco/swift-composable-architecture/pull/345 pls pls :) ), most of those breakpoints would fire as they wouldn't be cancelled as they are fired from the level above.
I still crave to get some feedback on my Navigation (Tests) explorations 🙏
Closing in favor of #1879.