Properties

Properties associate values with a particular class, structure, or enumeration. Stored properties hold a value as part of an instance; computed properties calculate a value on demand. Swift adds observers, wrappers, and type-level properties on top, giving you precise control over how data is stored and accessed.

Stored properties

A stored property is a constant (let) or variable (var) stored as part of an instance. Structures and classes can have them; enumerations cannot store data this way (they use associated values instead).

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}

var range = FixedLengthRange(firstValue: 0, length: 3)
range.firstValue = 6   // var → mutable
// range.length = 4  // ❌ error: length is a constant

Constant struct instances and stored properties

If you assign a struct instance to a constant, you cannot change any of its properties — even ones declared var — because the whole value is constant. This is a consequence of value semantics, and it does not apply to classes.

let fixed = FixedLengthRange(firstValue: 0, length: 4)
// fixed.firstValue = 5  // ❌ error: `fixed` is a constant value type

Lazy stored properties

A lazy stored property's initial value isn't computed until the first time it's accessed. Use it when the value is expensive to create or depends on state not known until after initialization. A lazy property must be a var.

class DataImporter {
    var filename = "data.txt"   // pretend this is slow to set up
}

class DataManager {
    lazy var importer = DataImporter()
    var data: [String] = []
}

let manager = DataManager()
manager.data.append("Some data")
// importer hasn't been created yet…
print(manager.importer.filename)   // now it is → data.txt

Watch out: a lazy property isn't safe to access from multiple tasks simultaneously while it's being initialized. In Swift 6's concurrency model, keep lazy properties on actor-isolated or single-threaded code paths.

Computed properties

Computed properties don't store a value. Instead they provide a getter and an optional setter to retrieve and set other properties indirectly. Classes, structures, and enumerations can all have them.

struct Point { var x = 0.0, y = 0.0 }
struct Size { var width = 0.0, height = 0.0 }

struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            Point(x: origin.x + size.width / 2,
                  y: origin.y + size.height / 2)
        }
        set {
            origin.x = newValue.x - size.width / 2
            origin.y = newValue.y - size.height / 2
        }
    }
}

var square = Rect(origin: Point(x: 0, y: 0), size: Size(width: 10, height: 10))
print(square.center)        // → Point(x: 5.0, y: 5.0)
square.center = Point(x: 15, y: 15)
print(square.origin)        // → Point(x: 10.0, y: 10.0)

Read-only computed properties and shorthand setters

A computed property with only a getter is read-only; you can drop the get keyword and write the expression directly. A setter whose parameter you don't name uses the default name newValue (as above).

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double { width * height * depth }   // read-only
}

print(Cuboid(width: 2, height: 3, depth: 4).volume)  // → 24.0

Property observers

Property observers run code in response to changes in a property's value. willSet runs just before the value is stored, and didSet runs immediately after. They're available on stored properties (except lazy) and on inherited properties you override.

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

let counter = StepCounter()
counter.totalSteps = 100
// → About to set total to 100
// → Added 100 steps

Inside willSet the incoming value is available as newValue; inside didSet the previous value is oldValue. You can rename these by writing a name in parentheses.

Property wrappers

A property wrapper adds a layer of separation between code that manages how a property is stored and the code that defines it. You write the management logic once in a wrapper type and reuse it with the @ syntax. SwiftUI's @State and @Binding are property wrappers.

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

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

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

struct Volume {
    @Clamped(0...10) var level: Int = 5
}

var v = Volume()
v.level = 99
print(v.level)   // → 10 (clamped to the range)

Projected values

A wrapper can expose extra functionality through a projected value, accessed with a $ prefix. You define it as a property named projectedValue on the wrapper.

@propertyWrapper
struct Audited {
    private var value: Int = 0
    private(set) var projectedValue = false   // "was it ever changed?"

    var wrappedValue: Int {
        get { value }
        set { value = newValue; projectedValue = true }
    }
}

struct Form { @Audited var age: Int }

var form = Form()
print(form.$age)   // → false
form.age = 30
print(form.$age)   // → true (the projected value)

Global and local stored properties

The capabilities above also apply to global variables (defined outside any function or type) and local variables (inside a function). Global constants and variables are always computed lazily, similar to lazy properties, but don't need the keyword. Local variables are never computed lazily.

Type properties

Type properties belong to the type itself rather than to any instance — there's exactly one copy shared by everything. Define them with static. For classes, use class instead of static to allow subclasses to override a computed type property. Stored type properties must always have a default value.

struct Physics {
    static let gravity = 9.81
    static var simulationSpeed = 1.0
}

class Shape {
    class var sides: Int { 0 }   // overridable by subclasses
}

print(Physics.gravity)   // → 9.81
Physics.simulationSpeed = 2.0

Tip: type properties are perfect for shared constants and configuration. A static let on a struct is also the idiomatic way to define a thread-safe constant in Swift 6.

Recap

You can now store values, compute them on the fly, react to changes with observers, factor out storage logic with property wrappers and projected values, and attach data to a type itself. Next you'll give your types behavior with methods.