Performance Considerations When Using SectionKit: 7 Optimization Strategies
Avoid heavy object instantiation in config(_:), enable size caching via SKHighPerformanceStore, and use incremental updates instead of full reloads to maintain 60fps scrolling in SectionKit.
SectionKit is designed for smooth scrolling and low-overhead updates, but achieving optimal performance requires understanding its architectural hotspots. When building complex collection views with the linhay/sectionkit repository, developers must account for how the framework handles cell configuration, size calculation, and memory management. This guide covers the critical performance considerations when using SectionKit, from anti-patterns to avoid in AGENTS.md to the high-performance caching APIs implemented in Sources/SectionKit/HighPerformance/.
Avoid Heavy Work Inside config(_:)
The framework creates cells and sections repeatedly during reloads. Instantiating expensive objects—especially DateFormatter, NumberFormatter, or complex view layouts—inside config(_:) executes on the main thread for every model and can dominate frame time.
The anti-pattern list in AGENTS.md explicitly warns:
“Never instantiate formatters in
config(_:). Usestatic.”
Reuse Expensive Objects
Create formatters once and reuse them across all cells:
// ❌ Bad – creates a new formatter for every cell
func config(_ model: Model) {
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd"
label.text = df.string(from: model.date)
}
// ✅ Good – static, reused across all cells
private static let sharedFormatter: DateFormatter = {
let f = DateFormatter()
f.dateFormat = "yyyy-MM-dd"
return f
}()
func config(_ model: Model) {
label.text = Self.sharedFormatter.string(from: model.date)
}
Enable High-Performance Size Caching
When cells use Auto Layout or complex view hierarchies, recomputing sizes for every item becomes a major bottleneck. SectionKit provides a size-caching store that memorizes results for a model-identifier and limit-size pair.
Core Caching Components
The implementation relies on two key files:
Sources/SectionKit/HighPerformance/SKKVCache.swift: A thin wrapper aroundNSCachethat handles expiration and thread-safety.Sources/SectionKit/HighPerformance/SKHighPerformanceStore.swift: The public façade exposing thecache(by:limit:calculate:)API.
Implementing Size Caching
Enable caching by calling setHighPerformance(.init()) and providing a unique identifier for each model:
// 1️⃣ Define a Hashable model
struct Photo: Hashable {
let id: UUID
let imageURL: URL
let title: String
}
// 2️⃣ Build the section with high-performance mode
let photoSection = PhotoCell.wrapperToSingleTypeSection()
.setHighPerformance(.init()) // turn on caching
.highPerformanceID { context in
context.model.id // unique per-item identifier
}
.config(models: photos)
// 3️⃣ Add to manager
manager.update([photoSection])
The framework queries SKHighPerformanceStore for a cached size before falling back to the expensive layout pass.
Profile Execution Time with SKPerformance
Debug builds can measure execution time using SKPerformance.duration to log millisecond counts and maintain running averages. This helps identify hot paths like model-to-cell mapping or image decoding.
Located in Sources/SectionKit/Common/SKPerformance.swift, the utility provides zero runtime cost in production builds when DEBUG is undefined.
let section = SKPerformance.shared.duration("Section setup") {
ItemCell.wrapperToSingleTypeSection()
.config(models: items)
}
manager.update([section])
Debug output appears as:
[SKPerformance] /Sources/Example/.../MyViewController.swift:123: Section setup: 耗时 0.0023 秒
Prevent Memory Retain Cycles in Closures
Section-level closures like onCellAction or model(_:displayedAt:) retain captured values. Strong references to the surrounding view controller create retain cycles, forcing the section hierarchy to stay alive and increasing memory pressure.
The AGENTS.md anti-patterns section explicitly warns:
“Never capture
selfstrongly in section closures.”
Always use [weak self]:
section.onCellAction(.selected) { [weak self] ctx in
guard let self = self else { return }
self.showDetail(for: ctx.model)
}
Optimize Layout and Plugin Interactions
Plugins modifying layout (pinning, decorations, alignment) remain cheap when they only affect layout attributes. However, mixing them with native UICollectionViewFlowLayout pinning options causes the layout engine to recompute attributes twice per frame, degrading scroll performance.
The anti-pattern list in AGENTS.md specifically prohibits:
“Never mix
SKCSectionPinOptionswith FlowLayout's own pinning.”
Prefer Batch Updates Over Full Reloads
SKCManager in Sources/SectionKit/Manager/SKCManager.swift supports incremental changes (insert, delete, reload) instead of calling reloadData. Incremental updates trigger only affected sections or rows to be re-measured and animated, reducing per-frame work.
manager.update(sections) // diff-based incremental update
// vs.
manager.reload(section) // full reload of the section
Summary
- Reuse expensive objects: Never instantiate
DateFormatteror heavy views insideconfig(_:); usestaticproperties instead. - Enable size caching: Use
setHighPerformance(.init())andhighPerformanceIDto leverageSKHighPerformanceStoreand avoid redundant Auto Layout passes. - Profile hot paths: Wrap suspicious code in
SKPerformance.shared.duration()to identify bottlenecks during development. - Prevent retain cycles: Always capture
[weak self]in section closures to avoid memory leaks and unnecessary layout passes. - Avoid layout conflicts: Do not mix
SKCSectionPinOptionswithUICollectionViewFlowLayoutpinning to prevent double attribute calculations. - Prefer incremental updates: Use
manager.update(sections)for diff-based changes rather than full reloads to minimize re-measurement work.
Frequently Asked Questions
How does SectionKit's size caching improve scrolling performance?
SectionKit's size caching stores calculated cell dimensions in SKHighPerformanceStore, backed by the NSCache wrapper SKKVCache. When scrolling, the framework checks for a cached size using the model's unique identifier before executing expensive Auto Layout calculations. This eliminates redundant measurement work during rapid scroll events, maintaining 60fps performance even with complex cell hierarchies.
What is the performance cost of using SKPerformance in production builds?
SKPerformance has zero runtime cost in production builds. The duration method and related APIs are compiled out when DEBUG is undefined, meaning the closure executes directly without timing overhead. This allows developers to instrument critical paths during development without worrying about shipping performance-monitoring code to users.
Why should I avoid instantiating DateFormatter inside config(_:)?
config(_:) executes on the main thread for every cell during reloads and scrolls. DateFormatter initialization is notoriously expensive due to internal locale and calendar setup. Creating a new instance for each cell causes frame drops during rapid scrolling. The recommended approach uses a static formatter property created once and reused across all cells, as documented in the AGENTS.md anti-patterns guide.
When should I use batch updates instead of reloadData?
Use batch updates via manager.update(sections) whenever the data set changes incrementally (insertions, deletions, moves, or updates to specific items). This triggers UICollectionView's batch update animations and only re-measures affected cells. Reserve reloadData or manager.reload(section) for complete data set replacements or when the data source structure changes fundamentally, as full reloads discard all existing cells and recompute the entire layout.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →