WithViewStore instantiation with @ObservedObject

Hi,

Looking at the code of WithViewStore, I was wondering if the instantiation of the ViewStore was a bad pattern or not?

Here is the constructor (I’ve removed the DEBUG stuff for the sake of clarity):

public struct WithViewStore<ViewState, ViewAction, Content> {
  private let content: (ViewStore<ViewState, ViewAction>) -> Content
  @ObservedObject private var viewStore: ViewStore<ViewState, ViewAction>

  init(
    store: Store<ViewState, ViewAction>,
    removeDuplicates isDuplicate: @escaping (ViewState, ViewState) -> Bool,
    content: @escaping (ViewStore<ViewState, ViewAction>) -> Content
  ) {
    self.content = content
    self.viewStore = ViewStore(store, removeDuplicates: isDuplicate)
  }

The viewStore is created in the init, although it is an ObservedObject. From what I understand, @ObservedObject should be injected and not created by the view because when init is called again (that can happen anytime) then the viewStore is created again. I thought that @StateObject was the way to go when we want the view to own the lifecycle of the created objects.

Everybody, what’s you take on take?

Hi! I think that the viewStore property gets the @ObservedObject wrapper here because one can scope/observe sub-stores when instantiating a WithViewStore object and unwrapping a given store.

Perhaps @mbrandonw or @stephencelis could explain what’s behind that choice and what are the consequences in terms of lifecycle?

@twittemb My understanding is the same as yours. I brought this up in Should viewStore be a StateObject? · Discussion #934 · pointfreeco/swift-composable-architecture · GitHub. Also there has been a recent branch created that changes this. wip · pointfreeco/swift-composable-architecture@6448766 · GitHub. Maybe it was done to remain compatible with iOS 13 since state object wasn’t available until iOS 14. I have not been using WithViewStore but creating a state object in the view initializer. This has been working for me.

1 Like

Thanks @kgrigsby59.

self._viewStore = StateObject(wrappedValue: ViewStore(store, removeDuplicates: isDuplicate))

I was interested in the kind of instantiation too. I’ve seen threads saying it is officially supported by Apple but discouraged in their documentation.

1 Like

officially supported by Apple but discouraged in their documentation.

If you know what are the consequences of doing like this (meaning that the ViewStore will only be instantiated once for the lifetime of the View), it will work as expected, and this is the only way to instantiate a StateObject from an init. But this "gotcha" is why this is not publicized a lot by Apple, even if they confirmed it would work fine in some SwiftUI lounge.

As for the rest of the discussion, iOS 13 support is indeed the only thing preventing to make WithViewStore use a @StateObject, but a few related performance improvements are already in preparation and can be released in the upcoming weeks if they don't have adverse effects.

As soon as TCA ends official support for iOS 13 (in one way or another), WithViewStore will be able to switch to @StateObject, provided this construct still makes sense at this moment.

1 Like

While it is true that observable objects are typically passed in from the parent rather than instantiated from inside, in this case it is ok. This is because the actual source of truth for the view is from the store, which is passed into the view.

And as @tgrapperon we do plan on using @StateObject inside WithViewStore, but more for performance reasons than correctness reasons. We won't do that until 1.0 probably where we will drop iOS 13 support.

1 Like

Thanks for all the nice answers.

1 Like