# Performance Considerations When Using SectionKit: 7 Optimization Strategies

> Optimize SectionKit performance with 7 strategies: avoid heavy instantiation, enable size caching, and use incremental updates. Achieve smooth 60fps scrolling.

- Repository: [神奇/sectionkit](https://github.com/linhay/sectionkit)
- Tags: performance
- Published: 2026-03-06

---

**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`](https://github.com/linhay/sectionkit/blob/main/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`](https://github.com/linhay/sectionkit/blob/main/AGENTS.md) explicitly warns:

> “Never instantiate formatters in `config(_:)`. Use `static`.”

### Reuse Expensive Objects

Create formatters once and reuse them across all cells:

```swift
// ❌ 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`](https://github.com/linhay/sectionkit/blob/main/Sources/SectionKit/HighPerformance/SKKVCache.swift)**: A thin wrapper around `NSCache` that handles expiration and thread-safety.
- **[`Sources/SectionKit/HighPerformance/SKHighPerformanceStore.swift`](https://github.com/linhay/sectionkit/blob/main/Sources/SectionKit/HighPerformance/SKHighPerformanceStore.swift)**: The public façade exposing the `cache(by:limit:calculate:)` API.

### Implementing Size Caching

Enable caching by calling `setHighPerformance(.init())` and providing a unique identifier for each model:

```swift
// 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`](https://github.com/linhay/sectionkit/blob/main/Sources/SectionKit/Common/SKPerformance.swift), the utility provides zero runtime cost in production builds when `DEBUG` is undefined.

```swift
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`](https://github.com/linhay/sectionkit/blob/main/AGENTS.md) anti-patterns section explicitly warns:

> “Never capture `self` strongly in section closures.”

Always use `[weak self]`:

```swift
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`](https://github.com/linhay/sectionkit/blob/main/AGENTS.md) specifically prohibits:

> “Never mix `SKCSectionPinOptions` with FlowLayout's own pinning.”

## Prefer Batch Updates Over Full Reloads

`SKCManager` in [`Sources/SectionKit/Manager/SKCManager.swift`](https://github.com/linhay/sectionkit/blob/main/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.

```swift
manager.update(sections)               // diff-based incremental update
// vs.
manager.reload(section)                // full reload of the section

```

## Summary

- **Reuse expensive objects**: Never instantiate `DateFormatter` or heavy views inside `config(_:)`; use `static` properties instead.
- **Enable size caching**: Use `setHighPerformance(.init())` and `highPerformanceID` to leverage `SKHighPerformanceStore` and 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 `SKCSectionPinOptions` with `UICollectionViewFlowLayout` pinning 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`](https://github.com/linhay/sectionkit/blob/main/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.