Proxy
A Proxy implements the same interface as a real service and controls access to it. It may enforce authorization, lazy-load a resource, or forward calls across a process or network boundary.
iOS problem and when to use it
An avatar picker needs photo-library data, but a feature should not bypass permission checks every time it asks for a thumbnail. A proxy can guard the service in one place.
Original example: permission-aware thumbnail access
import Foundation
enum PhotoAccessError: Error { case denied }
protocol ThumbnailLoading {
func thumbnail(for assetID: String) async throws -> Data
}
protocol PhotoAuthorization {
func hasReadAccess() async -> Bool
}
struct AuthorizedThumbnailProxy: ThumbnailLoading {
let authorization: any PhotoAuthorization
let realLoader: any ThumbnailLoading
func thumbnail(for assetID: String) async throws -> Data {
guard await authorization.hasReadAccess() else {
throw PhotoAccessError.denied
}
return try await realLoader.thumbnail(for: assetID)
}
}Callers depend on ThumbnailLoading whether they receive the proxy or a test fake. The proxy checks access on every request and only forwards authorized ones.
Production decisions
With PhotosUI's system picker, selected items can be accessed through picker APIs without requesting broad library access. Use this proxy only for a feature that genuinely needs direct PhotoKit access. Permission can change in Settings; check current status rather than caching a previous grant. A proxy differs from a decorator by controlling whether the real operation is reached at all.
Follow the access check
AuthorizedThumbnailProxy conforms to ThumbnailLoading, the same protocol as the real loader. Its thumbnail(for:) method first asks PhotoAuthorization for current access. If denied, it throws without touching the real loader. If permitted, it forwards the request and result. The caller can use the same interface in either case, but the proxy controls whether the underlying operation happens at all.
From example to photo selection
At the app boundary, wrap the PhotoKit loader with this proxy and inject it into the avatar feature. The view model can catch PhotoAccessError.denied and show a route to Settings or an explanation. Do not request broad photo-library permission just to use the system Photos picker; PhotosUI often provides selected items without that access. This proxy fits direct library browsing or asset-ID access where authorization is genuinely required.
Failure modes and verification
Test that denied access never calls the loader, granted access forwards the exact asset ID, and loader errors propagate unchanged. Permission can change while the app is backgrounded, so check at use time. A decorator also wraps the same interface, but it usually augments behavior such as caching; this proxy is a gate. Do not hide permission failure by returning empty image data, which looks like a successful load.