Scroll Performance with ForEachStore

I've been wrestling with this all day. I'm still fairly new to TCA in general, so maybe I'm misunderstanding how things are supposed to piece together here.

I have a FileManagerView with a ForEachStore inside a List like so:

List {
      state: \.shownFiles,
      action: FileManagerAction.fileListItem(id:action:)
    content: FileListItem.init(store:)

shownFiles is defined in FileManagerState as an IdentifiedArrayOf<FileListItemState>. The list renders the items as I would expect, but each item has some data that I want to fetch lazily as the user scrolls to reveal the file info. Things like on-disk size, attaching to the file representation's objectWillChange publisher, etc.

In FileListItem, I fire these initial setup things in its Reducer by sending the onAppear action in SwiftUI's onAppear() callback.

This is where things get hairy. By adding that onAppear hook, scroll performance is out the window! And I know it's not the effects that the Reducer fires off; the scroll jitters even when case .onAppear returns .none. It seems that the simple act of sending an action to the view store has a significant performance cost when many such calls are made in a row.

Instruments tells me that the heaviest callstacks bottom out in extractHelp in extract(case:from:), in a call to String(describing:), I guess while building something to do with a CasePath. I have no idea what it's doing, or why it's taking so long there in this case. I'm still new to TCA in general...

I've tried a few work-arounds:

  • Set a variable in the view state to check from the reducer, and optionally return .none. This was no good, as the performance hit is on the way to the reducer, not in the effects fired after.
  • Set a State variable on the FileListItem view to track whether we've already sent the onAppear action, so as to only fire it once. This is all well and good when scrolling back up the list, but scrolling down and firing those actions the first time still hurts. Not ideal.
  • Don't use ForEachStore. Surprisingly (to me at least), when I bypass the combined reducer and instead create a new Store for each item inside a regular ForEach block, scroll performance is excellent! My onAppear actions fire smoothly, one after the other, and my data starts to load right away. This isn't ideal either, because it means that I'm passing in a fresh environment, rather than using pullback or forEach in FileManagerView's reducer.
  • Go for a walk to clear my head. This actually was helpful, and led me to the last two workarounds above. Didn't help with scroll performance, though.

Any help or advice would be very much appreciated.

Terms of Service

Privacy Policy

Cookie Policy