---
title: Properties
source: 'https://academia.sh/en/courses/swift/properties'
course: 'The Swift Language'
language: en
updated: '2026-10-21T05:03:13+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Tells apart when a stored, a computed and an observed property set up their value'
    - 'Measures that a lazy property is evaluated once, on the first read'
    - 'Reads the difference between a default value and lazy from the output visible while instances are created'
    - 'Shows that a lazy property in a structure multiplies along with the copy'
---

# Properties

Measuring when stored, computed and observed properties, lazy properties and static let values are set up.

The previous lesson showed that the associated value of an enumeration is evaluated once, when the case is constructed, and that its computed property is evaluated on every read. The same distinction is not special to enumerations. The **properties** of structures, classes and enumerations name the data of a type, and each property sets up its value at its own time: some while the instance is created, some on every read, some on the first read.

This lesson measures four kinds of property. A **stored property** keeps a value inside the instance. A **computed property** stores no value and runs an expression when read. An **observed property** is a stored property that runs its `willSet` and `didSet` **property observers** before and after it changes. A **lazy property** does not set up its value until the first read. The course's measuring axis is the same in this lesson: **when is this expression evaluated?** There are four moments: immediately, on first use, on every use, never. The `Probe` class is given below exactly as it is.

## The Core

Every lesson uses the same core. `Probe` counts how many times an expression is evaluated (`eval`) and how many times it is used (`use`); the `moment` property derives the moment from those two numbers. In the binding measurement of the opening lesson, the distribution over eight expressions was 2/1/3/2: two immediately, one on first use, three on every use, two never. In this lesson we measure only our own slice.

```swift
// Probe.swift — records when an expression is evaluated
final class Probe {
    let label: String
    private(set) var evaluations = 0
    private(set) var uses = 0
    private var evaluatedBeforeUse = false

    init(_ label: String) {
        self.label = label
    }

    func eval<T>(_ value: T) -> T {
        evaluations += 1
        if uses == 0 { evaluatedBeforeUse = true }
        return value
    }

    func use<T>(_ access: () -> T) -> T {
        uses += 1
        return access()
    }

    var moment: String {
        if evaluations == 0 { return "never" }
        if evaluations > 1 { return "on-every-use" }
        return evaluatedBeforeUse ? "immediately" : "on-first-use"
    }

    func report() {
        print("\(label): \(evaluations) evaluations, \(uses) uses -> \(moment)")
    }
}

func tally(_ probes: [Probe]) {
    for moment in ["immediately", "on-first-use", "on-every-use", "never"] {
        let count = probes.filter { $0.moment == moment }.count
        print("\(moment): \(count)")
    }
}
```

## Three Basic Kinds

The first measurement puts stored, computed and observed properties side by side. In the `Rectangle` structure `width` and `height` are stored properties, and `area` is a computed one: it holds no value and does the multiplication on every read. `target` in the `Thermostat` class is a stored property that carries `willSet` and `didSet` observers. `willSet` sees the upcoming value under the name `newValue` before the value is written, and `didSet` sees the old value under the name `oldValue` after it is written.

```swift
// main.swift — stored, computed and observed property
struct Rectangle {
    var width: Int
    var height: Int

    var area: Int {
        width * height
    }
}

final class Thermostat {
    var target = 20 {
        willSet { print("willSet", target, "->", newValue) }
        didSet { print("didSet", oldValue, "->", target) }
    }

    init(target: Int) {
        self.target = target
    }
}

let rect = Rectangle(width: 3, height: 4)
print(rect.area)
let thermostat = Thermostat(target: 18)
print("initializer done")
thermostat.target = 22
thermostat.target = 25
```

```
12
initializer done
willSet 18 -> 22
didSet 18 -> 22
willSet 22 -> 25
didSet 22 -> 25
```

The first line gives the `3 * 4` result of the `area` property. Before the second line, the initializer had written `18` into the `target` field, but the observers did not run: no line appeared until the initializer finished. Observers run only on assignments made after the instance is fully set up; an assignment in the type's own initializer does not trigger them. The next two assignments ran the observers, and for each assignment the order was `willSet`, the assignment, `didSet`. In the `willSet` output the right-hand value shows the upcoming value, and in the `didSet` output the left-hand value shows the old value; in the second, since `target` is already updated, the old value could not have been reached without the name `oldValue`.

The three kinds answer three different questions. A stored property answers "where does the value sit?". A computed property answers "how is the value derived from other values?". An observed property answers "what should happen when the value changes?". None of them is lazy: the stored value is set up at construction, the computed value on every read, and the observer on every write.

**TS18.** This measurement is for a computed property in a structure and an observed property in a class. Observers in structures, the direction of change of a computed property, and the behavior of observers on a property passed with `inout` were not measured separately.

## Computed Properties and set

A computed property is not only read, it can be written too. For this, a `set` is written next to the `get`; `set` takes the assigned value under the name `newValue` and is obliged to reflect it into the stored properties. In the `Temperature` structure, `fahrenheit` neither stores a value nor is a source itself. When read it is derived from `celsius`, and when written it updates `celsius`.

```swift
// main.swift — a computed property tied to a stored field with get and set
struct Temperature {
    var celsius: Double

    var fahrenheit: Double {
        get { celsius * 9 / 5 + 32 }
        set { celsius = (newValue - 32) * 5 / 9 }
    }
}

var reading = Temperature(celsius: 100)
print(reading.fahrenheit)
reading.fahrenheit = 32
print(reading.celsius)
```

```
212.0
0.0
```

With `celsius` at `100`, reading `fahrenheit` gave `212.0`. Then `32` was written to `fahrenheit` and the `set` block pulled `celsius` to `0.0`. A single piece of data can be read and written in two different representations, and the real value sits in one place, the `celsius` field. This is the strength of a computed property: it offers two forms of the same information without causing inconsistency.

The same result follows from the enumeration example in the previous lesson: a computed property runs again on every read. The difference here is that with `set` it also works in the opposite direction.

## Lazy Properties

Some properties are expensive and not needed in every instance. Computing them while the instance is created is wasted work for an instance that will never be read. The `lazy` keyword leaves the property unset until the first read. The first read evaluates the expression once and stores the result inside the property, and later reads give the stored value. The `summary` property in the `Report` class computes the total of the rows lazily.

```swift
// main.swift — a lazy property is evaluated once, on the first read
final class Report {
    let probe: Probe
    var rows = [1, 2, 3]
    lazy var summary: Int = probe.eval(rows.reduce(0, +))

    init(probe: Probe) {
        self.probe = probe
    }
}

let summaryProbe = Probe("lazy-first-read")
let report = Report(probe: summaryProbe)
_ = summaryProbe.use { report.summary }
_ = summaryProbe.use { report.summary }
summaryProbe.report()
```

```
lazy-first-read: 1 evaluations, 2 uses -> on-first-use
```

One evaluation, two reads, moment **on first use**. The computation was done at the first read and the second read gave the stored result. This is what separates it from a computed property: a computed property would have made two evaluations in two reads. `lazy` is not a computed property, it is a deferred stored property. That is why `lazy` has to be a `var`; because the first read writes the value into the property, a writable stored property is needed.

## The Difference Between a Default Value and lazy

In the previous lesson we measured that a default value is evaluated once per instance. Now we write the same expression in two forms so the difference becomes visible. The `Ticket` class carries two properties: `eagerId` calls the same `makeId` function through a default value, and `lazyId` calls it through `lazy`, and the function prints a line every time it is called. Two tickets are created, and then only the first ticket's `lazyId` property is read twice.

```swift
// main.swift — a default value runs at creation, lazy at the first read
func makeId(_ kind: String) -> Int {
    print("built", kind)
    return 7
}

final class Ticket {
    var eagerId = makeId("eager")
    lazy var lazyId = makeId("lazy")
}

let tickets = [Ticket(), Ticket()]
print("created")
print(tickets[0].lazyId)
print(tickets[0].lazyId)
```

```
built eager
built eager
created
built lazy
7
7
```

The first two lines show that the `eagerId` property of both tickets was set up before `created` was printed: each instance computed its own default value at creation time. For `lazyId` there are no such lines. The `lazy` line appeared only when `tickets[0].lazyId` was first read, and it appeared only once; the second read gave `7` from the stored value. The `lazyId` property of the second ticket was never read, so it was never set up.

The difference is summarized briefly. The expression of a default value runs for every instance, while the instance is being created. A `lazy` expression runs only for the instances that are read, at the first read. If twenty tickets are created and only one's id is looked at, `lazy` does one computation instead of twenty.

## static let

Values that belong to the type, that is `static` properties, are bound not to instances but to the type itself. A value defined with `static let` is lazy: it is set up on first access, and set up once. `Config.version` is built with a closure, and the closure prints a line each time it runs.

```swift
// main.swift — static let is set up on first access
enum Config {
    static let version: String = {
        print("static let built")
        return "1.0"
    }()
}

print("before")
print(Config.version)
print(Config.version)
```

```
before
static let built
1.0
1.0
```

The `before` line was printed before the first access; the `static let built` line appeared only when `Config.version` was first read, and it appeared only once. The later read gave the value `1.0` from the stored copy. This is the same behavior as `lazy`, but there is one difference: `static let` is not tied to an instance, and unlike `lazy` it does not have to be a `var`, so it can be used as an immutable lazy constant.

**TS19.** This measurement is for a `static let` set up with a closure. `static var` and `static` computed properties were not measured separately.

## The Bounding Measurement

The rule we built for the `lazy` property is this: the expression is evaluated once, on the first read. This rule holds for a class instance, because a class instance is a single shared object. We move the same property into a structure. Structures are copied on assignment, and a copy carries its own copy of a `lazy` property that has not been read yet. In the `Sheet` structure, `total` is a lazy property; `sheetA` is copied and both copies are read once.

```swift
// main.swift — a lazy property in a structure multiplies with the copy
struct Sheet {
    let probe: Probe
    var rows = [1, 2, 3]
    lazy var total: Int = probe.eval(rows.reduce(0, +))
}

let sheetProbe = Probe("struct-lazy-copy")
var sheetA = Sheet(probe: sheetProbe)
var sheetB = sheetA
_ = sheetProbe.use { sheetA.total }
_ = sheetProbe.use { sheetB.total }
sheetProbe.report()
```

```
struct-lazy-copy: 2 evaluations, 2 uses -> on-every-use
```

Two reads, two evaluations, moment **on every use**. Both copies set up the same `lazy` property separately, because the property had not been set up yet at the moment of copying, and each copy received its own unset property. The same value was computed twice.

This result narrows the lesson's rule. The word "once" holds for one instance. In a structure there are as many instances as copies, and a `lazy` property that has not yet been read is set up independently in each copy at its first read. In a class this situation does not arise, because no copy appears.

A `lazy` property in a structure has a second limit, and it too cannot be measured, because it does not compile. Reading writes to the property, so it requires the structure to be mutable. The fragment below reads the `lazy` property of a structure defined with `let` and does not compile: the compiler rejects it. The error message said that a mutating read cannot be used on a structure defined with `let`. The fragment stands here only for explanation.

```swift
struct Sheet2 {
    var rows = [1, 2, 3]
    lazy var total: Int = rows.reduce(0, +)
}
let fixed = Sheet2()
print(fixed.total)   // does not compile: a lazy property cannot be read on a let structure
```

This is a side consequence of the `lazy` property: the read includes a write. A structure instance meant to be read has to be a `var`. A class does not have the same restriction, because a class name defined with `let` fixes only the name, not the content of the object.

**TS20.** This measurement is for two copies and a single `lazy` property. Nested structures, cases where a `lazy` property depends on another `lazy` property, and, in the rejected fragment, only a `let` structure were tried; other ways of fixing a value were not tried.

## Summary

- A stored property is set up at construction, a computed property on every read, and an observer on every write; an assignment in the type's own initializer does not trigger observers.
- A computed property can be written with `set` and reflects the real value into another stored property.
- A `lazy` property is evaluated once, on the first read; a default value runs for every instance during creation.
- `static let` is set up on first access, and set up once.
- A not-yet-read `lazy` property in a structure multiplies with the copy; it cannot be read on a `let` structure.
- This lesson's moment: **on first use**. A `lazy` property is evaluated once, on the first read.

## Next Step

In this lesson we measured when properties set up their value. Types do not only carry data, they can also promise behavior. The next lesson measures protocols: the contracts that say which properties and methods a type must provide, and the moment at which a property is evaluated when it is called from code that uses the contract.
