State
State changes an object's allowed behavior based on its current state. Swift enums are often a clearer implementation than a hierarchy of state classes because the compiler can enforce exhaustive transitions.
When it helps in iOS
A file upload can be idle, uploading, paused, completed, or failed. A single isLoading Boolean cannot express whether Resume or Retry should be available.
Original example: upload transitions
import Foundation
enum UploadState {
case idle
case uploading(progress: Double)
case paused(progress: Double)
case completed
case failed(message: String)
var canResume: Bool {
if case .paused = self { return true }
return false
}
mutating func pause() {
guard case .uploading(let progress) = self else { return }
self = .paused(progress: progress)
}
mutating func resume() {
guard case .paused(let progress) = self else { return }
self = .uploading(progress: progress)
}
}
var state: UploadState = .uploading(progress: 0.35)
state.pause()
state.resume()The enum carries the data relevant to each state. SwiftUI can switch over it to show a progress bar, Resume button, success view, or error message.
Production decisions
Keep state transitions in the upload owner, not scattered across view button actions. The underlying URLSessionTask must actually suspend and resume when the state changes; this enum alone only models UI state. Return an error for invalid transitions when callers need to know why an action failed.
Make legal behavior explicit
UploadState encodes mutually exclusive phases in one enum. Only uploading and paused states carry progress, so the model cannot accidentally claim completion and still show an active percentage. pause() only changes an uploading state; resume() only changes a paused state. canResume lets the view decide whether to display or enable the Resume control without duplicating transition logic in UI code.
From example to a real upload
A view model owns the state and the underlying URLSessionTask. When the user pauses, it asks the task to suspend and then updates state if that operation succeeds. Progress callbacks update .uploading(progress:) on the main actor. On failure, retain a useful error category and enough request information to retry. If a background transfer continues after the app is suspended, restore its state from the session rather than assuming the last in-memory enum value remains true.
Failure modes and verification
The sample mutating methods silently ignore invalid transitions; a production API may return an error for an impossible action. Test every transition, especially pause after completion, retry after failure, and cancellation during upload. State differs from Strategy: State changes behavior because the object's lifecycle changed; Strategy chooses among interchangeable algorithms, often based on user preference.