Repository navigation
0.4.1
Performance
NSOutlineViewDiffableDataSource.applySnapshot was priced by the shape of the tree rather than by the
size of the change. Two separate costs are gone.
The part that grew with the number of parents. Every apply recomputed the section snapshot's root
array from scratch — once per inserted row, because NSOutlineView asks child:ofItem: for each one.
Collecting the visible rows then called parentOfChildItem: per row, which scans backwards to node 0
for a root item, giving O(roots × parents). Expansion-state sync hashed every identifier in the
snapshot. Root arrays and visible items are now computed once per state (the state is immutable, so
the cache cannot go stale), the visible-row walk derives parents from a level stack instead of asking
the tree, and expansion sync addresses the snapshotter by index. A tree with 761 parents now costs
what the same data costs with one parent.
The part that did not depend on the change at all. An empty diff still cost about 6 ms. Four
things happened on every apply regardless: a fresh top-level snapshot was rebuilt, _itemToSection
was torn down and refilled (in sectionless mode the answer is always the same sentinel), the batch
engine tested every visible row for parent addressability including rows with no children, and the
sentinel section was fed to expandItem:, which sends AppKit through its lazy-item lookup. The
top-level snapshot is now materialised on read, only parents that actually have child rows are
settled, and neither the section map nor expandItem: is touched in sectionless mode.
Measured against 0.4.0 with an external A/B harness — 1269 roots, 761 parents, 3466 items, children
collapsed, three runs each, alternating:
| 0.4.0 | 0.4.1 | |
|---|---|---|
| apply, all items | 14.48 ms | 2.44 ms |
| apply, filtered subset | 7.36 ms | 2.54 ms |
| apply, empty diff | 8.04 ms | 2.26 ms |
Normalised against each framework's own hand-written reloadData baseline on the same machine at the
same time, this port scores 0.19 where UICollectionViewDiffableDataSource scores 0.62; at 0.4.0 it
was 1.01. Absolute numbers are from a loaded machine and are conservative.
NSBrowserDiffableDataSource builds its parent index the same way and got the same treatment.
Behaviour change
After a top-level apply, snapshot() no longer carries the reload requests that apply already
performed. Previously the data source kept the caller's snapshot object as its current snapshot,
and any reconfigureItemsWithIdentifiers: / reloadItemsWithIdentifiers: recorded on it stayed
there. Taking snapshot(), editing it and applying it back — the ordinary way to use this API — would
therefore redo the previous apply's reloads on top of your own.
If you were relying on that to re-run a reload, record it again on the new snapshot. Nothing else
about snapshot() changed: it still reports each section as that section's visible items.
Compatibility
Everything here is internal. No public API was added, removed or renamed, no type layout moved, and
the export table is unchanged — existing binaries keep working without recompiling.
Warning
AppKitPlus is in testing. No API or ABI stability is promised.
Any release may remove classes, change type layouts, or change protocol requirements, with no
deprecation period. Pin an exact version and read these notes before upgrading.
Installation
.package(url: "https://github.com/AppKitSupportProgram/AppKitPlus-Release", from: "0.4.1")Artifact
| Platform | macOS 12.0+ |
| Architectures | arm64, arm64e, x86_64 |
| Built with | Xcode 26.6 Build version 17F113 |
| SHA-256 | 9f0f4c926ab9e7e3e1b3636f84415860f017f91cc1b605dc52d8aa14a0af3dc8 |