Sample CodeiOS, iPadOS, Mac CatalystReviewed 2026-07-21View on Apple Developer

Prefetching collection view data

At a glance

Item Summary
Purpose Starts expensive cell data work before rows become visible and cancels work that scrolling makes unnecessary.
App architecture One CustomDataSource is both the collection view data source and prefetch data source; it delegates asynchronous, cached, identifier-based work to AsyncFetcher.
Main patterns Prefetch adapter, cache-aside fetch, request deduplication/coalescing, cancellable command, and cell-reuse identity guard.
Project style Small storyboard-based UIKit app with screen composition, collection adaptation, asynchronous infrastructure, operation, cell, and display-data files.

Project structure

CollectionViewPrefetching/
├── AppDelegate.swift
├── ViewController.swift
├── CustomDataSource.swift
├── AsyncFetcher.swift
├── AsyncFetcherOperation.swift
├── Cell.swift
├── DisplayData.swift
└── Base.lproj/
    ├── Main.storyboard
    └── LaunchScreen.storyboard

Structure observations

  • ViewController.swift performs screen wiring only; data-source and loading policy live outside the controller.
  • CustomDataSource.swift is the boundary between index paths/cells and stable model identifiers.
  • AsyncFetcher.swift owns shared fetch coordination, while AsyncFetcherOperation.swift represents one cancellable unit of work.
  • Cell.swift and DisplayData.swift keep presentation state separate from request scheduling.

Overall architecture

Reference code

CollectionViewPrefetching/CustomDataSource.swift:40 — the adapter converts an index path to a stable identifier, checks the cache, and rejects a completion if cell reuse changed that identity.

let model = models[indexPath.row]
let identifier = model.identifier
cell.representedIdentifier = identifier

// Check if the `asyncFetcher` has already fetched data for the specified identifier.
if let fetchedData = asyncFetcher.fetchedData(for: identifier) {
    // The system has fetched and cached the data; use it to configure the cell.
    cell.configure(with: fetchedData)
} else {
    // There is no data available; clear the cell until the fetched data arrives.
    cell.configure(with: nil)

    // Ask the `asyncFetcher` to fetch data for the specified identifier.
    asyncFetcher.fetchAsync(identifier) { fetchedData in
        DispatchQueue.main.async {
            /*
             The `asyncFetcher` has fetched data for the identifier. Before
             updating the cell, check whether the collection view has recycled it to represent other data.
             */
            guard cell.representedIdentifier == identifier else { return }
            
            // Configure the cell with the fetched image.
            cell.configure(with: fetchedData)
        }
    }
}

Interpretation

Prefetching is an advisory input into the same identifier-based fetch path used by visible cells. A prefetched result can become a cache hit later; a visible-cell request can join an in-flight fetch; cancellation removes work and callbacks for identifiers no longer expected onscreen.

Ownership and state

Ownership evidence

CollectionViewPrefetching/AsyncFetcher.swift:14 — the fetcher privately retains the concurrency, callback, and cache state needed to coordinate all requests.

/// A serial `OperationQueue` to lock access to the `fetchQueue` and `completionHandlers` properties.
private let serialAccessQueue = OperationQueue()

/// An `OperationQueue` that contains `AsyncFetcherOperation`s for requested data.
private let fetchQueue = OperationQueue()

/// A dictionary of arrays of closures to call when an object has been fetched for an id.
private var completionHandlers = [UUID: [(DisplayData?) -> Void]]()

/// An `NSCache` used to store fetched objects.
private var cache = NSCache<NSUUID, DisplayData>()

// MARK: Initialization

init() {
    serialAccessQueue.maxConcurrentOperationCount = 1
}
Owner State Relationship Mutation authority
ViewController One CustomDataSource Creates and privately retains it Controller setup only
CustomDataSource Model array and fetcher Creates and exclusively exposes their behavior through callbacks Data-source/prefetch methods
AsyncFetcher Serial queue, fetch queue, handler table, cache Long-lived coordinator for this data source Its fetch/cancel/private helper methods
AsyncFetcherOperation Identifier and fetched result One operation per in-flight identifier main() writes the result
Cell representedIdentifier and displayed data Reused view state, not model ownership Data source assigns/configures it

The collection view stores callback references to the same adapter. The sample’s lifecycle ownership remains ViewControllerCustomDataSourceAsyncFetcher.

Class and protocol design

Type Responsibility Design note
ViewController Connect the storyboard collection view to one adapter final; no fetch policy in the screen controller
CustomDataSource Supply cells and translate prefetch/cancel index paths into identifiers Conforms to both UICollectionViewDataSource and UICollectionViewDataSourcePrefetching
CustomDataSource.Model Give each logical item a stable UUID Minimal nested value because only the adapter uses it
AsyncFetcher Deduplicate work, coalesce callbacks, cache results, and cancel by identifier Facade over queues, operations, and cache
AsyncFetcherOperation Simulate one slow, cancellable fetch Operation command with a readable result
Cell Display DisplayData and expose current represented identity Identity supports safe asynchronous reuse
DisplayData Carry the fetched presentation value Small result object cached by NSCache

There is no app-defined protocol. The architectural contracts are UIKit’s two collection-view protocols and Foundation’s Operation lifecycle; protocol-oriented design here means one concrete adapter fulfills two related framework roles.

Access control

Symbol Access Verified effect Rationale
ViewController.dataSource private Only the controller can retain or replace its adapter. Prevents outside code from bypassing screen wiring.
CustomDataSource.models / asyncFetcher private Index-path mapping and fetch implementation stay behind callback methods. Keeps collection protocol behavior as the public shape inside the target.
Fetch queues, handlers, cache, and helper methods private Callers can request/cache-read/cancel but cannot mutate coordination state directly. Protects deduplication and callback invariants.
AsyncFetcherOperation.fetchedData private(set) Other target code may read a completed result; only the operation writes it. Expresses producer ownership.
Feature types and most methods implicit internal Visible within the sample target, not exported as a library API. The sample demonstrates an app implementation, not a reusable public package.

Reference code

CollectionViewPrefetching/AsyncFetcherOperation.swift:10 — the operation exposes identity and read access to its output while retaining write authority.

class AsyncFetcherOperation: Operation {
    // ...
}

No declaration is public or open; private(set) is the only deliberately asymmetric setter boundary.

Logic ownership and placement

Logic Owner Why it belongs there
Storyboard/data-source wiring ViewController It knows the concrete collection view and screen lifecycle.
Index path → identifier mapping and cell reuse checks CustomDataSource It owns models and receives cell/prefetch callbacks.
Prefetch start and cancellation CustomDataSource UIKit supplies index paths through that protocol boundary.
Request deduplication, handler coalescing, cache, and serialized bookkeeping AsyncFetcher These concerns span visible and speculative requests.
Slow work and cancellation checkpoint AsyncFetcherOperation One operation encapsulates one command’s execution lifecycle.
Presentation reset/configuration Cell The reusable view owns its visual state, not the fetch lifecycle.

Design patterns

Pattern Source evidence Purpose or tradeoff
Dual-role adapter CollectionViewPrefetching/ViewController.swift:20 One object presents items and receives anticipatory loading hints, avoiding parallel index mapping.
Cache-aside CollectionViewPrefetching/CustomDataSource.swift:44 Visible-cell lookup uses cached data immediately and fetches only on a miss.
Request deduplication CollectionViewPrefetching/AsyncFetcher.swift:90 An existing operation for a UUID prevents duplicate work.
Callback coalescing CollectionViewPrefetching/AsyncFetcher.swift:41 Multiple consumers join one identifier’s handler array.
Cancellable command CollectionViewPrefetching/AsyncFetcher.swift:70 Prefetch cancellation locates and cancels an Operation, then clears callbacks.
Reuse identity guard CollectionViewPrefetching/CustomDataSource.swift:53 Late results cannot paint a cell now representing another model.

Naming conventions

  • Role suffixes communicate framework boundaries: ViewController, CustomDataSource, AsyncFetcherOperation, and Cell.
  • Asynchronous intent is explicit in fetchAsync; cache inspection and cancellation use fetchedData(for:) and cancelFetch(_:).
  • representedIdentifier names view identity rather than implying the cell owns a model.
  • Queue names distinguish purpose: serialAccessQueue protects bookkeeping, while fetchQueue executes slow work.
  • Model.identifier, operation identifier, and cell representedIdentifier keep the same identity concept traceable across layers.

Architecture takeaways

  • Feed visible-cell requests and speculative prefetches through one stable-identifier pipeline.
  • Treat prefetching as an optimization: correctness still comes from cache checks and normal cell configuration.
  • Coalesce duplicate requests and keep cancellation bookkeeping serialized.
  • Carry model identity into reusable views and verify it before applying asynchronous results.
  • Use private(set) when consumers need results but only the producer should mutate them.

Source map

Source file Relevant symbols
CollectionViewPrefetching/ViewController.swift Composition and dual data-source assignment
CollectionViewPrefetching/CustomDataSource.swift Models, cell configuration, prefetch, cancellation, reuse guard
CollectionViewPrefetching/AsyncFetcher.swift Cache, queues, deduplication, handler coalescing
CollectionViewPrefetching/AsyncFetcherOperation.swift Identifier-scoped cancellable work
CollectionViewPrefetching/Cell.swift Reuse identifier and display configuration
CollectionViewPrefetching/DisplayData.swift Cached presentation result
CollectionViewPrefetching/AppDelegate.swift Application entry point