Skip to content

Version 2 Roadmap

Joseph Duffy edited this page Nov 2, 2020 · 2 revisions

As a concept Composed is great, but it falls down when not used in the ideal way. This is particularly true for consumers that update frequently (e.g. via a web socket providing frequent data updates) or do not use Core Data and so have to perform diffing manually. The main issues ran in to are:

  • Crashes when performing many updates at once, often caused by the collection view's internal state differing from Composed's state
  • Inflexibility of API (particularly delegates)
  • Lack of understanding of pitfalls that can hurt performance

Changing the mappings to be per-section rather than at the top level was a good move but it was not enough to avoid crashes and performance hits when the UI was added

I will outline what I think a roadmap for a v2 release could be. Hopefully we can comment on wiki pages. If not this will be moved to an issue. I'd like to use GitHub Discussions but the beta appears to require 3 contributors. I went for a wiki because then we can both edit it (unlike an issue message).

Removing complexity of delegate calls "up"

Delegate calls "up" the chain (e.g. eventually to UICollectionView) look a little complex. For example in SectionProviderUpdateDelegate the Section that was inserted/deleted it passed up. This appears to be mainly for setting the updateDelegate to be the SectionProviderMapping. As outlined below I think the SectionProviderMapping could be removed, allowing the section's delegate to be the SectionProvider and simplifying the delegate.

Removal of SectionProviderMapping

Some of the recent improvements to ComposedSectionProvider might allow for the removal of SectionProviderMapping. When originally proposing the architecture of each layer performing mappings I did not envision the top level SectionProviderMapping being necessary. Having a single type could've meant that the bugs in ComposedSectionProvider would've been caught earlier since SectionProviderMapping already has tests (which ComposedSectionProvider now has anyway).

There may be some extra use of SectionProviderMapping that I'm not seeing but to me it currently looks superfluous.

Removal, improvement, and/or heavy discouragement of invalidateAll

invalidateAll is used in a few places in the core of Composed but poses a huge performance hit when used frequently. This is mainly because it will invalidate the entire tree of sections, despite only a single section being invalidate.

One solution could be to remove it and perform a simple remove all then add all in the sections where it's currently used. At least this should improve the performance hit.

Another solution could be to update all of Composed to not use invalidateAll and heavily warn against its use in the docs. For example I was hit by ArraySection.replaceSubrange(_:with:) using invalidateAll, which I thought would be quite efficient!

Ideally this would be entirely removed once diffing is introduced.

Better understanding of consumer's responsibilities

The main thing I think about regarding this is the need for CollectionSelectionHandler.sizingStrategy(at:metrics:environment:) to be cached by the implementer. I understand why this is the case but it would be nicer to have these requirements outlined somewhere if the API can't be improved to accommodate them.

Delegate timings

The easiest way to crash now is to perform multiple delegate calls in quick succession. Mainly because the UI (e.g. UICollectionView) has its own internal state vs what Composed has. These issues are outlined in https://github.com/composed-swift/ComposedUI/issues/13 and https://github.com/composed-swift/ComposedUI/issues/8.

I'm unsure what the best fix for this would be. I changed the internals in https://github.com/composed-swift/Composed/pull/14 but this makes implementing sections a huge pain and could have real issues when consumers try to implement their own sections.

Balanced calls to start/end updating

Currently calling willBeginUpdating sets a flag to "pause" updates and then didEndUpdating will trigger the updates. But calling willBeginUpdating multiple times does not create a need to call didEndUpdating multiple times. This often requires consumers to create their own sections that mimic ArraySection just to have all updates performed in a single update. Making these calls balanced could improve this situation.

Using an updates closure

In the same way performBatchUpdates of UICollectionView asks you to pass a closure that they'll call, we could do the same. The main advantage to this is that the data updates would be synchronised with the UI. https://github.com/composed-swift/Composed/pull/14 is kind of like this, except the sections are keeping track of pending vs applied data, which as mentioned is not the best for an implementation point of view.

This for me is the hardest problem to solve.

Bonus: Data Mixing

Arguably the best feature of Composed is the separation of the sections, but it's quite tricky to mix types within a section. Mixing types can be useful for certain kinds of layouts, e.g. to get sticky headers for free all cells must be in the same section, but even a somewhat simple data structure can prevent this.

Providing a Section that composes 2 or more sections and performs the mapping of indexes, delegate calls etc. would allow for this kind of UI to be created while maintaining the separation of sections. It could be very similar ComposedSectionProvider but conforming to Section rather than SectionProvider.

I don't think this is possible now because the section itself is passed up the delegate chain and either a === is used or something else gets confused. I've tried it and got strange crashes so gave up. With the above delegate changes I would hope this would be possible.

This could be part of where the function builder API is added.


I would summarise the tasks to be:

v2.0

  1. Copy over Section, SectionProvider, AggregateSectionProvider, ArraySection, ComposedSectionProvider, and SegmentedSectionProvider.
  2. Copy SectionUpdateDelegate and SectionProviderUpdateDelegate. But for now leave out willBeginUpdating, didEndUpdating, and invalidateAll. Remove sections: [Section] from SectionProviderUpdateDelegate functions.
  3. Add tests for copied over types
  4. Add delegate methods for updates
  5. Add UICollectionView support

v2.1

  1. Add some form of diffing support (even if only in ArraySection)
  2. Support "data mixing" e.g. ComposedSection