Mastering Properties in Swift: A Comprehensive Guide
Swift properties are fundamental to how you store, retrieve, and manage state within your apps. Understanding their different forms—from simple stored properties to powerful computed and observed ones—is key to building robust and performable applications. This guide covers everything you need to know.

Introduction to Properties in Swift
In Swift, properties associate values with a particular class, structure, or enumeration instance. They can be constants or variables, and can be categorized into several types, each serving a distinct purpose in your application's architecture.
Understanding properties is crucial for managing data and state effectively. Whether you're building a simple struct or a complex class, properties are the backbone of your data models. Swift's property system is designed to be safe, declarative, and highly flexible, offering features like automatic synthesis for certain property types and powerful observation capabilities. This allows you to react to changes in your data, making your applications more dynamic and responsive.
This article will guide you through the various types of properties available in Swift, providing clear explanations and practical code examples to demonstrate their usage and benefits.
Stored Properties: The Basics of Data Storage
Stored properties are the simplest form of properties. They store a constant or variable value as part of an instance of a class or structure. You can provide a default value when defining a stored property, or you can initialize it within an initializer.
Stored properties can be let (constants) or var (variables). A let stored property's value cannot be changed after initialization, while a var stored property's value can be modified at any time.
Here's how you can define and use stored properties in both structures and classes. Remember that for structures, if all stored properties have default values, Swift automatically synthesizes a memberwise initializer for you. For classes, you typically provide your own initializers.
Computed Properties: Dynamic Value Calculation
Computed properties do not store a value directly. Instead, they provide a getter (to retrieve the property's value) and an optional setter (to set other properties indirectly). This allows you to calculate a value on demand, based on other properties, or to perform actions when a property is accessed or changed.
Computed properties are particularly useful when a property's value can be derived from existing data, avoiding redundancy and ensuring consistency. They are always declared with the var keyword because their value is not fixed.
If a computed property only has a getter, you can omit the get keyword. This creates a read-only computed property. If it has both a getter and a setter, the setter can implicitly be provided with a new value using the name newValue (though you can specify a custom name if preferred).
Property Observers: Reacting to Value Changes
Property observers allow you to respond to changes in a property's value. They are called just before a property's value is set (willSet) and immediately after a property's value is set (didSet). Property observers are not called for properties with initializers when the property is first set during initialization.
willSet is called just before the value is stored. It provides access to the new value that's about to be set, available as newValue (or a custom name you provide).
didSet is called immediately after the value is stored. It provides access to the old value that was just replaced, available as oldValue (or a custom name).
Property observers are extremely useful for tasks like updating UI elements, performing validation, logging changes, or triggering other actions when a specific piece of data changes. You can attach property observers to any stored properties you define, except lazy stored properties. You can also add them to any inherited stored properties or inherited computed properties by overriding the property.
Lazy Stored Properties: Deferred Initialization
A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the lazy modifier before its declaration.
Lazy properties are useful for properties that:
- Require significant setup and configuration that might not be needed immediately.
- Depend on other parts of the instance that are not fully initialized until after the instance itself has been constructed.
- Are large objects or resources that are infrequently used.
Only var stored properties can be declared lazy. let properties cannot be lazy because they must have a value before initialization completes, and lazy properties are not guaranteed to have a value until accessed. Also, lazy cannot be used with computed properties because their value is always calculated upon access.
Note: If a lazy property is accessed by multiple threads simultaneously before it has been initialized, there's no guarantee that the property will be initialized only once. Be mindful of multi-threading contexts.
Type Properties: Shared Values Across All Instances
Type properties are properties that belong to the type itself, rather than to any one instance of that type. They are declared with the static keyword for both classes and structures/enumerations. For classes, you can also use the class keyword for computed type properties to allow subclasses to override them.
Type properties are useful for defining values that are universal to all instances of a given type, such as global constants, configuration settings, or shared resources. For example, you might use a type property to keep track of the total number of instances created for a class.
Stored type properties are always lazy, even if you don't write the lazy modifier, and their initialization is thread-safe. They are initialized only when they are first accessed.
Global and Local Variables vs. Properties
While not strictly properties in the context of classes or structs, it's worth noting the similarity and distinction between properties, global variables, and local variables.
- Global variables are defined outside of any function, method, closure, or type context. They are always stored variables, and their values can be accessed and modified from anywhere in your code. Like type properties, Swift's global variables are lazy and thread-safe by default.
- Local variables are defined within a function, method, or closure. They exist only for the lifetime of that scope. They cannot have property observers attached.
The key difference is scope and ownership. Properties are owned by instances of a type (or the type itself for type properties), whereas global and local variables are independent of type instances. You should generally prefer properties for managing state within your types, as they offer better encapsulation and organization.
Key Path Expressions for Properties
Swift 4 introduced Key Path Expressions, (\TypeName.propertyName), which provide a way to refer to properties dynamically. They are particularly useful when working with frameworks like SwiftUI or when you need to store references to properties for later use, enabling more generic and reusable code.
A key path expression returns a KeyPath instance, which is a generic type where Root is the type the key path refers to (e.g., Person or Book) and Value is the type of the property at the end of the key path (e.g., String or Int). There are specialized key path types like WritableKeyPath and ReferenceWritableKeyPath for properties that can be written to.
This functionality is robust and type-safe, preventing common runtime errors associated with string-based key-value coding (KVC) in Objective-C. You can use key paths to read and write property values, or even chain them to access properties within properties.
Conclusion
Swift's property system is a powerful and flexible mechanism for defining and managing the state of your applications. From the straightforward data storage of stored properties to the dynamic value generation of computed properties, and the reactive capabilities of property observers, Swift provides a rich set of tools.
Choosing the right type of property for each situation is a crucial aspect of writing clean, efficient, and maintainable Swift code. By effectively leveraging stored, computed, lazy, and type properties, you can create robust data models and responsive user interfaces that stand the test of time.
Continue exploring the Swift documentation and experimenting with these concepts in your own projects to deepen your understanding and master the art of property management in Swift.
Common Interview Questions
What is the difference between a stored property and a computed property?
A **stored property** holds a value in memory as part of an instance of a class or struct. It's like a typical variable. A **computed property** does not store a value; instead, it calculates and returns a value every time it's accessed (via its `getter`) and can optionally set other properties when a new value is assigned to it (via its `setter`). Computed properties are always `var` and must provide at least a `getter`.
When should I use `willSet` and `didSet` property observers?
You should use `willSet` and `didSet` when you need to perform actions *before* or *after* a property's value changes, respectively. Common use cases include: updating UI elements, validating new values, logging changes, or notifying other parts of your app about a change. For example, `didSet` is excellent for pushing new data to a UI when a model property updates, while `willSet` could be used for pre-change validation.
Can I have lazy computed properties or lazy `let` properties?
No, you cannot. `lazy` can only be applied to **stored properties** declared with `var`. `let` properties must have their value initialized before the instance's initialization completes, which contradicts the concept of lazy initialization. Computed properties are already 'lazy' in a sense, as their value is re-calculated every time they are accessed; they do not store a value, so `lazy` has no meaning for them.