Properties and Property Wrappers

13.1 Stored, Computed and Lazy Properties

struct Rectangle {
    var width: Double                          // stored
    var height: Double
    var area: Double { width * height }        // computed (read-only)
    var perimeter: Double {
        get { 2 * (width + height) }
        set { width = newValue / 4; height = newValue / 4 }   // computed setter
    }
}

class DataLoader {
    lazy var cache: [String: Data] = loadCache()  // computed on first access
    private func loadCache() -> [String: Data] { [:] }
}

13.2 Property Observers

class StepCounter {
    var steps: Int = 0 {
        willSet { print("About to set to \(newValue)") }
        didSet {
            if steps > oldValue { print("Added \(steps - oldValue) steps") }
        }
    }
}

13.3 Type Properties

struct AppConfig {
    static let apiVersion = "v2"                 // shared by the type (lazily, thread-safely initialized)
    static var requestCount = 0
    static var baseURL: String { "https://api.example.com/\(apiVersion)" }
}
class Theme {
    class var accentColor: String { "blue" }     // overridable in subclasses
}

13.4 Property Wrappers

Familiar wrappers: SwiftUI's @State, @Binding, @Environment, @AppStorage, SwiftData's @Query, swift-argument-parser's @Option/@Flag.

@propertyWrapper
struct Clamped<Value: Comparable> {
    private var value: Value
    let range: ClosedRange<Value>

    init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }

    var wrappedValue: Value {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }

    var projectedValue: ClosedRange<Value> { range }   // accessed with $
}

struct Volume {
    @Clamped(0...100) var level: Int = 50
}
var v = Volume()
v.level = 150
print(v.level, v.$level)    // 100 0...100

@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T
    var wrappedValue: T {
        get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }
}
struct Settings {
    @UserDefault(key: "darkMode", defaultValue: false) static var darkMode: Bool
}