-
Notifications
You must be signed in to change notification settings - Fork 3
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
Add collection view batch updates #15
Conversation
This is only required for the tests, so I don' think the `from` needs to be updated in `Package.swift` since consumers never run tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so its obviously a lot more complex and likely this is necessary. I'm never a big fan of index tracking since its very easy to break.
Could we improve this with perhaps a type that maintains an index. Then include a few accessors like:
stack.push()
stack.pop()
stack.isEmpty
EDIT: I'm obviously referring to the +=
and -=
balance calls in the mapping.
@@ -192,31 +199,40 @@ open class CollectionCoordinator: NSObject { | |||
delegate?.coordinatorDidUpdate(self) | |||
} | |||
|
|||
fileprivate func debugLog(_ message: String) { | |||
if #available(iOS 12, *) { | |||
os_log("%@", log: OSLog(subsystem: "ComposedUI", category: "CollectionCoordinator"), type: .debug, message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
let cell = self.collectionView.cellForItem(at: indexPath) else { | ||
indexPathsToReload.append(indexPath) | ||
continue | ||
!section.prefersReload(forElementAt: indexPath.item), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what's going on here with the whitespace?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how Xcode indents when copy/pasting or using re-indent.
I've reverted it, although I think it's clearer when some extra line breaks are added and running re-indent:
guard
let section = self.sectionProvider.sections[indexPath.section] as? CollectionUpdateHandler,
!section.prefersReload(forElementAt: indexPath.item),
let cell = self.collectionView.cellForItem(at: indexPath)
else {
indexPathsToReload.append(indexPath)
continue
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's fine.
I'm not enjoying it and I'm worried it's fragile, but the (pretty large and growing) test suite makes me feel a little better about it.
I thought that tracking some sort of index (I was using the index provided by the mapping) would be useful/required, but the issue is that we don't know the order change could occur so I don't think there's a single index that can be tracked that helps. At first I was going to create a allChanges.map { change in
guard change.section > removedSection else { return change }
var change = change
change.section -= 1
return change
} But this isn't actually that useful and could cause more confusion because of differences with how changes are handled (see how sections removes modify other section removes for example) I have been thinking about some sort of |
reset() | ||
prepareSections() | ||
collectionView.reloadData() | ||
} | ||
|
||
public func mappingWillBeginUpdating(_ mapping: SectionProviderMapping) { | ||
reset() | ||
defersUpdate = true | ||
batchedUpdatesCount += 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps I'm misunderstanding but for the index stack I suggested, I meant for this value? Not sure how your response relates? Maybe you can elaborate here.
Also, yeah reuse across other coordinators makes total sense and esp from a testing POV.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I thought you were referencing the index changes performed in each of the update delegate methods.
Moving batchedUpdatesCount
to the Changeset
type mentioned is part of the plan for that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok cool.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only one question on the review. Otherwise, looks much better. Purposeful at least and definitely more testable and reusable.
EDIT: Approved by accident hahah!
XCTAssertTrue(changesReducer.changeset.elementsUpdated.isEmpty) | ||
XCTAssertTrue(changesReducer.changeset.elementsMoved.isEmpty) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume you plan on adding more test cases later on?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only the start -- the CollectionCoordinator
doesn't use it yet.
More tests will be added to this file before merging, but I think it's going to lean a little on the new CollectionCoordinator
tests passing to validate correctness according to UICollectionView
.
XCTAssertTrue(changeset!.groupsUpdated.isEmpty) | ||
} | ||
|
||
func testRemoveAMovedIndex() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shaps80 This test, along with testMoveElementAtSameIndexAsRemove
below, are currently failing but I'm not 100% sure about the tests themselves.
Can you confirm that the test looks correct? e.g. this should produce removes and inserts, not a remove and moves?
Moving to main repo with merged libraries: composed-swift/Composed#28 |
The idea of this is that without breaking the API the collection view will batch updates.
Inserting a large number of sections is the main area of performance loss we are currently encountering, because the sections are inserted individually and not batched. This change alone has reduce the initial load time of one our screens (which has 100-150 sections added at once) from 30-45 seconds down to less than a second (at least it is not noticeable).
I had created composed-swift/Composed#17 to try and address this, which has the advantage that it would apply to other view types (e.g.
UITableView
), but I believe does not offer the same performance improvements and it is restricted to a singleComposedSectionProvider
.This is a draft to collect feedback; as you can see there are some TODOs but I think there's enough implemented to provide an overview of the changes that would be required to implement this.
This does not currently work; there are situations that cause the collection
NSInternalInconsistencyException', reason: 'Invalid update
error. I have some failing tests that demonstrate what the result should be.