At a glance
| Item |
Summary |
| Purpose |
Keeps a destination grid responsive by prefetching, preparing, caching, and canceling large image work. |
| App architecture |
The scene injects model/asset stores into a grid controller; the controller coordinates diffable UI and request lifetimes; AssetStore owns a multi-tier image pipeline. |
| Main patterns |
Dependency injection, diffable data source, prefetch/cancel, memory + file caches, request sharing, and protocol-backed stores. |
| Project style |
Programmatic UIKit feature grouped into Stores, Utilities, and Views, with Combine used for cancellable pipelines. |
Project structure
CollectionViewSample/
├── AppDelegate.swift
├── SceneDelegate.swift
├── DestinationPost.swift
├── PostGridViewController.swift
├── Stores/
│ ├── SampleData.swift
│ ├── ModelStore.swift
│ └── AssetStore.swift
├── Utilities/
│ ├── MemoryLimitedCache.swift
│ ├── FileBasedCache.swift
│ ├── PlaceholderStore.swift
│ ├── UnfairLock.swift
│ └── URLSession+DownloadTaskPublisher.swift
└── Views/
├── DestinationPostCell.swift
├── DestinationPostPropertiesView.swift
└── SectionBackgroundView.swift
Structure observations
- Stores own lookup and asset preparation; view files own drawing/layout and cell request lifetime.
- Utility caches separate memory pressure, disk I/O, downsampling, and locking concerns.
SceneDelegate is the composition root; AppDelegate only supplies scene lifecycle configuration.
Overall architecture
flowchart LR
Scene["SceneDelegate"] -->|"inject stores"| Grid["PostGridViewController"]
Grid --> Sections["AnyModelStore<Section>"]
Grid --> Posts["AnyModelStore<DestinationPost>"]
Grid --> Assets["AssetStore"]
Grid --> Diffable["Diffable data source"]
Diffable --> Cell["DestinationPostCell"]
Grid -->|"prefetch / cancel"| Assets
Cell -->|"retains Cancellable"| Assets
Assets --> Memory["MemoryLimitedCache"]
Assets --> Placeholder["PlaceholderStore"]
Assets --> Disk["FileBasedCache"]
Assets --> Prepare["download + preparingForDisplay"]
Prepare --> Disk
Prepare --> Memory
Reference code
CollectionViewSample/PostGridViewController.swift:53 — cell registration joins model lookup, placeholder delivery, asynchronous loading, cancellation-token transfer, and item reconfiguration.
let cellRegistration =
UICollectionView.CellRegistration<DestinationPostCell, DestinationPost.ID> {
[weak self] cell, indexPath, postID in
guard let self = self else { return }
let post = self.postsStore.fetchByID(postID)
let asset = self.assetsStore.fetchByID(post.assetID)
var token = self.prefetchingIndexPathOperations
.removeValue(forKey: indexPath) ?? cell.assetToken
if asset.isPlaceholder && token == nil {
token = self.assetsStore.loadAssetByID(post.assetID) {
[weak self] in self?.setPostNeedsUpdate(postID)
}
}
cell.assetToken = token
cell.configureFor(post, using: asset)
}
Interpretation
The controller owns UI scheduling, not image-processing details. It asks the asset store for the best immediately available representation, carries a cancellable from prefetch to the visible cell, and reconfigures only the affected identifier when the prepared image arrives.
Ownership and state
classDiagram
PostGridViewController o-- AnyModelStore : injected section and post stores
PostGridViewController o-- AssetStore : injected
PostGridViewController *-- UICollectionViewDiffableDataSource : UI snapshot owner
PostGridViewController *-- AnyCancellable : prefetch operations by IndexPath
DestinationPostCell *-- Cancellable : current asset assertion/request
AssetStore *-- MemoryLimitedCache : prepared images
AssetStore *-- FileBasedCache : local originals
AssetStore *-- PlaceholderStore : downsampled placeholders
AssetStore *-- SharedPublisher : in-flight request per asset ID
MemoryLimitedCache *-- AssetDictionary : cached assets
MemoryLimitedCache *-- AssertionCounts : protected asset IDs
Ownership evidence
CollectionViewSample/Stores/AssetStore.swift:100 — one shared publisher is retained per asset ID and removed when the pipeline completes.
private var requestsCache:
[Asset.ID: Publishers.Share<AnyPublisher<Asset, Never>>] = [:]
func prepareAssetIfNeeded(
id: Asset.ID
) -> Publishers.Share<AnyPublisher<Asset, Never>> {
if let request = requestsCache[id] {
return request
}
let publisher = prepareAsset(id: id)
.subscribe(on: Self.networkingQueue)
.receive(on: DispatchQueue.main)
.handleEvents(receiveCompletion: { [weak self] _ in
self?.requestsCache.removeValue(forKey: id)
})
.assertNoFailure("Downloading Asset (id: \(id)")
.eraseToAnyPublisher()
.share()
requestsCache[id] = publisher
return publisher
}
| Owner |
Object or state |
Relationship |
Mutation authority |
SceneDelegate |
Window and root grid |
Constructs feature graph |
Scene connection callback |
PostGridViewController |
Collection view, diffable source, injected stores, prefetch-token dictionary |
Retains UI/session dependencies |
Controller setup, cell registration, prefetch callbacks |
DestinationPostCell |
Image/properties views and current asset token |
Owns visible request/assertion lifetime |
Configuration, reuse, deinit |
AssetStore |
Prepared cache, disk caches, placeholder queue, in-flight publisher map |
Owns image delivery pipeline |
Fetch/load/prepare methods |
MemoryLimitedCache |
Assets, memory usage, assertion counts, memory-warning subscription |
Owns memory residency policy |
Lock-protected cache operations |
FileBasedCache / PlaceholderStore |
Cache directory and on-disk image representation |
Own disk-layer behavior |
Fetch/move/downsample operations |
Class and protocol design
| Type or protocol |
Responsibility |
Depends on or conforms to |
ModelStore |
Synchronous lookup of an Identifiable model by ID |
Associated type |
AnyModelStore<Model> |
Dictionary-backed concrete model lookup |
ModelStore |
Cache |
Optional cached lookup by model ID |
Associated type |
MemoryLimitedCache |
Prepared-image cache with assertions and pressure purging |
Cache, UnfairLock |
FileBasedCache |
Load/store assets by deterministic file URL |
Cache, FileManager |
PlaceholderStore |
Downsample downloaded originals into small future placeholders |
FileBasedCache, ImageIO |
AssetStore |
Resolve best current image and coordinate prepare/download/cache stages |
ModelStore, Combine |
PostGridViewController |
Build adaptive layout/diffable snapshots and bridge prefetch to assets |
UICollectionViewDataSourcePrefetching |
The local protocols capture two narrow lookup capabilities. Despite its name, AnyModelStore is not a general closure-based type eraser; it is a concrete generic dictionary store that satisfies ModelStore.
Access control
| Symbol |
Access |
Verified effect |
Why it fits this design |
| Grid stores, collection view, and data source |
implicit internal |
Other target code can inspect or replace them. |
Keeps the teaching sample simple, though production code could narrow setters. |
| Prefetch dictionary and section-kind string |
fileprivate |
Shared by the controller’s same-file extensions, unavailable to other files. |
The file is split into setup, prefetch, and layout extensions. |
| Configuration/layout/update helpers |
private |
Stay behind controller lifecycle and callbacks. |
Prevents other types from driving partial UI setup. |
AssetStore cache layers and request map |
private |
Callers use fetch/load rather than manipulating cache policy. |
Protects the multi-tier pipeline. |
placeholderStore |
Declared public let |
Read-only reference outside AssetStore; effective reach is still limited by the internal class/app module. |
Exposes the sample’s generated placeholder store without allowing replacement. |
MemoryLimitedCache.currentMemoryUsage |
private(set) |
Readable for diagnostics; only the cache updates accounting. |
Preserves memory-usage invariants. |
| Cache asset dictionaries, lock, assertion counts |
private |
Mutation must pass through lock-aware methods. |
Avoids unsynchronized state. |
DestinationPostCell.assetToken / configureFor |
Declared public |
Available to controller code, but effectively internal with the internal cell type. |
Makes the cell’s small integration surface explicit. |
Reference code
CollectionViewSample/Utilities/MemoryLimitedCache.swift:26 — externally readable memory accounting surrounds private, lock-guarded residency state.
class MemoryLimitedCache: Cache {
let memoryLimit: Measurement<UnitInformationStorage>
private(set) var currentMemoryUsage =
Measurement<UnitInformationStorage>.zero
private let accessLock = UnfairLock()
private var assets: [Asset.ID: Asset] = [:]
private var protectedAssetIDs: [Asset.ID: Int] = [:]
private var cancellation = [AnyCancellable]()
}
Logic ownership and placement
| Logic |
Owning type or file |
Placement rationale |
| Root dependency construction |
SceneDelegate |
Scene lifetime determines the root controller graph. |
| Snapshot/layout/cell/prefetch coordination |
PostGridViewController |
Requires collection-view identifiers and visibility context. |
| Request sharing, cache ordering, image preparation |
AssetStore |
One service enforces the asset-delivery policy. |
| Memory accounting, assertions, purging |
MemoryLimitedCache |
Residency policy needs one lock-protected owner. |
| Disk URL and image decoding |
FileBasedCache |
Independent persistence concern. |
| Placeholder downsampling |
PlaceholderStore |
Specialized disk-cache behavior, not controller work. |
| Request cancellation on reuse |
DestinationPostCell |
Cell lifetime is the exact boundary for visible asset use. |
Design patterns
| Pattern |
Source evidence |
Purpose or tradeoff |
| Constructor injection |
CollectionViewSample/SceneDelegate.swift:17 |
Makes the grid’s store dependencies explicit and test-replaceable at construction. |
| Diffable data source |
CollectionViewSample/PostGridViewController.swift:76 |
Applies identity-based snapshots and targeted reconfiguration. |
| Prefetch and cancellation |
CollectionViewSample/PostGridViewController.swift:106 |
Couples speculative work to collection-view callbacks. |
| Cell-scoped resource token |
CollectionViewSample/Views/DestinationPostCell.swift:24 |
Cancels or releases the asset assertion on reuse/deinit. |
| Multi-tier cache |
CollectionViewSample/Stores/AssetStore.swift:41 |
Serves prepared, placeholder, local, then downloaded representations. |
| Shared publisher |
CollectionViewSample/Stores/AssetStore.swift:100 |
Deduplicates concurrent work for the same asset ID. |
| Lock-protected cache |
CollectionViewSample/Utilities/MemoryLimitedCache.swift:31 |
Keeps accounting and eviction state coherent across access. |
| Protocol-backed store |
CollectionViewSample/Stores/ModelStore.swift:10 |
Defines lookup capability separately from dictionary storage. |
Naming conventions
- Storage roles use
Store or Cache: AssetStore, AnyModelStore, PlaceholderStore, and MemoryLimitedCache.
- Pipeline methods describe phases:
fetchByID, loadAssetByID, prepareAssetIfNeeded, and makeDownloadRequest.
DestinationPostCell and DestinationPostPropertiesView name the domain element plus UI role.
- Boolean/state names reveal policy:
isPlaceholder, currentMemoryUsage, protectedAssetIDs, and prefetchingIndexPathOperations.
Architecture takeaways
- Inject stores at the scene/controller boundary and keep cache mechanics out of cells and view controllers.
- Tie asynchronous work to UI lifetime with cancellables that move from prefetch ownership to cell ownership.
- Deduplicate work by stable asset ID and reconfigure diffable items by identity.
- Separate prepared-memory, original-disk, and placeholder-disk policies.
- Use
private(set) and private dictionaries to protect accounting and concurrency invariants.
Source map
| Source file |
Relevant symbols |
CollectionViewSample/SceneDelegate.swift |
Composition root and store injection |
CollectionViewSample/PostGridViewController.swift |
Diffable UI, layout, prefetch/cancel |
CollectionViewSample/Stores/ModelStore.swift |
ModelStore and dictionary-backed store |
CollectionViewSample/Stores/AssetStore.swift |
Asset pipeline and shared requests |
CollectionViewSample/Utilities/MemoryLimitedCache.swift |
Locked memory residency and assertions |
CollectionViewSample/Utilities/FileBasedCache.swift |
Disk cache |
CollectionViewSample/Utilities/PlaceholderStore.swift |
Placeholder downsampling |
CollectionViewSample/Views/DestinationPostCell.swift |
Cell layout and resource-token lifetime |