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.