Skip to main content

Working with Properties

1. Using Stored Properties

Property Type Description Example
storedProperty String A variable property that stores a value directly. var name: String = "Swift"
constantProperty Int A constant property that is initialized once and cannot be changed. let count: Int = 10

Example: Declaring and Using Stored Properties

struct Person {
    var name: String = "John"
    let age: Int = 30
}

var person = Person()
print(person.name) // Prints "John"
person.name = "Jane"
print(person.name) // Prints "Jane"

2. Using Lazy Stored Properties

Property Type Description Notes
lazy Lazy Declares a property that is initialized only when accessed for the first time Must be declared as var and cannot be let

Example: Lazy Property Initialization

class DataProcessor {
    lazy var expensiveData = fetchData()
    
    func fetchData() -> [String] {
        // Simulate expensive operation
        return ["item1", "item2", "item3"]
    }
}

let processor = DataProcessor()
print("Processor created") // Printed immediately
print(processor.expensiveData) // fetchData() called here
Note: The lazy property is initialized only once, the first time it's accessed. Subsequent accesses return the cached value.

3. Using Computed Properties

Property Type Description Example
computedProperty Int A property that is computed from other properties or values.
var computedProperty: Int {
    get {
        return 42
    }
}
read-only computed property String A computed property with only a getter, making it read-only.
var readOnlyProperty: String {
    return "Hello, World!"
}
read-write computed property Double A computed property with both getter and setter.
var readWriteProperty: Double {
    get {
        return value * 2.0
    }
    set(newValue) {
        value = newValue / 2.0
    }
}

Example: Calculating Area of a Rectangle

struct Rectangle {
    var width: Double = 0.0
    var height: Double = 0.0
    
    var area: Double {
        return width * height
    }
}

let rect = Rectangle(width: 5.0, height: 3.0)
print(rect.area) // Output: 15.0

Example: Temperature Conversion

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

var temp = Temperature(celsius: 0.0)
print(temp.fahrenheit) // Output: 32.0

temp.fahrenheit = 98.6
print(temp.celsius) // Output: 37.0

4. Using Property Observers

Observer Type Description Syntax Notes
willSet Executes before a property's value is changed willSet { ... } Can access the new value via newValue
didSet Executes after a property's value is changed didSet { ... } Can access the old value via oldValue

Example: Using Property Observers

class Temperature {
    var celsius: Double {
        willSet {
            print("Setting temperature to \(newValue)°C")
        }
        didSet {
            print("Temperature changed from \(oldValue)°C to \(celsius)°C")
        }
    }

    init(celsius: Double) {
        self.celsius = celsius
    }
}

let temp = Temperature(celsius: 20.0)
// Output:
// Setting temperature to 25.0°C
// Temperature changed from 20.0°C to 25.0°C

temp.celsius = 25.0
Note: Property observers are not called when a property is first initialized.

5. Understanding Property Wrappers

Property wrappers in Swift allow you to reuse code for managing property access and storage. They provide a way to encapsulate common behaviors like validation, observing changes, or lazy initialization.

Feature Description Example
@PropertyWrapper Defines a custom property wrapper type.
@propertyWrapper
struct NonNegative<T: BinaryInteger> {
    private var value: T = 0
    
    var wrappedValue: T {
        get { value }
        set { value = max(0, newValue) }
    }
}
@ObservedObject Used in SwiftUI to observe changes in observable objects.
struct ContentView: View {
    @ObservedObject var user: User
    
    var body: some View {
        Text(user.name)
    }
}
@StateObject Initializes and manages a state object in SwiftUI.
struct ContentView: View {
    @StateObject private var userManager = UserManager()
    
    var body: some View {
        Text(userManager.currentUser.name)
    }
}

Example: Using a Custom Property Wrapper

struct User {
    @NonNegative var age: Int = 0
}

let user = User()
user.age = -5 // age is set to 0 due to the wrapper
print(user.age) // Output: 0
Note: Property wrappers are syntactic sugar that simplify how you interact with properties, but they don't change the underlying storage or access logic.

6. Using Built-in Wrappers

Wrapper Description Usage Example Notes
@State Manages the state of a view that can change and triggers UI updates @State private var isToggled = false Must be declared inside a View struct
@Binding Creates a two-way binding between views @Binding var title: String Used in child views to modify parent state
@ObservedObject Observes an observable object for changes @ObservedObject var user = User() Used with ObservableObject
@StateObject Initializes and manages an observable object @StateObject private var userManager = UserManager() Used to create and manage a new instance
@Environment Accesses environment values like theme or locale @Environment(\.colorScheme) var colorScheme Provides access to system or custom environment values
@EnvironmentObject Accesses an environment object from a parent view @EnvironmentObject var appState: AppState Used to pass data down the view hierarchy

Example: Using @State and @Binding

struct ContentView: View {
    @State private var isToggled = false
    
    var body: some View {
        Toggle("Toggle", isOn: $isToggled)
    }
}

struct ToggleView: View {
    @Binding var isOn: Bool
    
    var body: some View {
        Button("Toggle") {
            isOn.toggle()
        }
    }
}

Example: Using @ObservedObject and @StateObject

class User: ObservableObject {
    @Published var name = "John"
}

struct ContentView: View {
    @StateObject private var user = User()
    
    var body: some View {
        Text(user.name)
    }
}
Note: @StateObject should only be used once to initialize an object, while @ObservedObject is used for observing already initialized objects.
Warning: Do not use @State in a parent view and bind it to a child view using $ unless the child is designed to modify the state.

7. Creating Custom Property Wrappers

Property wrappers in Swift allow you to reuse code that manages how properties are stored and accessed. You can define your own property wrappers by creating a type that conforms to the PropertyWrapper protocol.

Requirement Description
wrappedValue The value that the property wrapper manages. This is accessed through the projectedValue property.
projectedValue Optional property that provides access to the wrapper's projected value, accessible via $ prefix.

Example: Basic Property Wrapper

@propertyWrapper
struct NonEmpty<T> {
    private var value: T?
    
    init(wrappedValue: T?) {
        self.value = wrappedValue
    }
    
    var wrappedValue: T? {
        get { value }
        set { value = newValue }
    }
    
    var projectedValue: Bool {
        value != nil
    }
}

struct User {
    @NonEmpty var name: String?
    @NonEmpty var email: String?
}

let user = User(name: "John", email: "john@example.com")
print(user.$name) // true

Example: Property Wrapper with Initialization

@propertyWrapper
struct DefaultValue<T> {
    private var value: T
    
    init(wrappedValue: T) {
        self.value = wrappedValue
    }
    
    var wrappedValue: T {
        get { value }
        set { value = newValue }
    }
    
    var projectedValue: T {
        get { value }
        set { value = newValue }
    }
}

struct Settings {
    @DefaultValue(wrappedValue: "Default") var theme: String
    @DefaultValue(wrappedValue: 0) var fontSize: Int
}

let settings = Settings()
print(settings.theme) // "Default"
Note: The projectedValue property is optional and can be used to provide additional functionality or access to the wrapper's state, such as validation flags or metadata.

8. Using Type Properties

Property Type Description Example
static static Defines a type property that belongs to the type itself, not instances.
class MyClass {
    static var count = 0
}
class class Defines a type property that can be overridden by subclasses.
class BaseClass {
    class var name: String {
        return "Base"
    }
}

Example: Accessing Type Properties

class Counter {
    static var count = 0
    
    init() {
        Counter.count += 1
    }
}

print(Counter.count) // Output: 0

let c1 = Counter()
print(Counter.count) // Output: 1
Note: Type properties are lazily initialized and are not computed until the first time they're accessed.

9. Understanding Property Requirements in Protocols

Requirement Type Description Example
Stored Property Property that stores a value var name: String
Computed Property Property that computes a value var fullName: String { get { return "\(firstName) \(lastName)" } }
Property Observer Property that observes changes var count: Int { didSet { print("Count changed to \(count)") } }
Note: Protocols define property requirements with var, not let or const.

Example: Protocol with Property Requirements

protocol Vehicle {
    var numberOfWheels: Int { get set }
    var maxSpeed: Double { get }
}

struct Car: Vehicle {
    var numberOfWheels = 4
    let maxSpeed: Double = 120.0
}

10. Using Key Paths

Property Type Description
keyPath KeyPath<Root, Value> A key path that refers to a property or nested property in a type

Example: Basic Key Path Usage

struct Person {
    var name: String
    var age: Int
}

let person = Person(name: "Alice", age: 30)
let keyPath = \Person.name
print(person[keyPath: keyPath]) // Output: "Alice"

Example: Nested Key Path

struct Address {
    var street: String
}

struct Person {
    var address: Address
}

let person = Person(address: Address(street: "123 Main St"))
let keyPath = \Person.address.street
print(person[keyPath: keyPath]) // Output: "123 Main St"

Example: Key Path with Function

struct Person {
    var name: String
    var age: Int
}

let person = Person(name: "Bob", age: 25)
let keyPath = \Person.age
let value = person[keyPath: keyPath]
print(value) // Output: 25
Note: Key paths are a powerful feature for accessing properties dynamically, particularly useful in SwiftUI and when working with KVO (Key-Value Observing).

Was this article helpful?