Skip to content

Variables and Constants

Swift uses var for mutable variables and let for immutable constants, promoting immutability to reduce bugs and improve code clarity. This document explores declaration, properties, observers, and advanced usage.

Declaring Variables and Constants

  • Variables (var): Mutable, can be reassigned.
  • Constants (let): Immutable, set once and cannot change.

Example: Basic Declaration:

swift
var counter = 0 // Mutable
let pi = 3.14159 // Immutable
counter += 1 // Valid
// pi = 3.14 // Error: Cannot assign to value: 'pi' is a 'let' constant

Use Case: Constants for Fixed Values:

swift
let maxRetries = 3
let appName = "MyApp"
// Avoids accidental modification

Type Annotations

Explicitly specify types with a colon (:) for clarity or when inference is ambiguous.

Example:

swift
var score: Int = 100
let message: String = "Game Over"
var coordinates: (x: Double, y: Double) = (0.0, 0.0)

Type Inference

Swift infers types from initial values, reducing boilerplate.

Example:

swift
let count = 42 // Inferred as Int
let price = 19.99 // Inferred as Double
let names = ["Alice", "Bob"] // Inferred as [String]

Ambiguity Resolution:

swift
let value: Double = 10 // Explicit type to avoid Int inference

Properties in Types

Swift supports several property types in structs, classes, and enums.

Stored Properties

Hold values directly.

Example:

swift
struct Rectangle {
    var width: Double
    var height: Double
}

var rect = Rectangle(width: 5.0, height: 3.0)
rect.width = 6.0 // Updates stored property

Computed Properties

Calculate values dynamically, requiring get and optionally set.

Example:

swift
struct Circle {
    var radius: Double
    var area: Double {
        get {
            return .pi * radius * radius
        }
        set(newArea) {
            radius = sqrt(newArea / .pi)
        }
    }
}

var circle = Circle(radius: 2.0)
print(circle.area) // 12.566370614359172
circle.area = 25.0
print(circle.radius) // ~2.820947917738781

Lazy Stored Properties

Initialize only on first access, marked with lazy. Useful for expensive computations.

Example:

swift
class DataLoader {
    lazy var heavyData: [Int] = {
        print("Loading data...")
        return Array(1...1_000_000)
    }()
}

let loader = DataLoader()
print("Before access")
print(loader.heavyData.count) // "Loading data...", then 1,000,000

Property Observers

Trigger code before (willSet) or after (didSet) a stored property changes.

Example:

swift
struct Player {
    var score: Int {
        willSet {
            print("Score will change to \(newValue)")
        }
        didSet {
            print("Score changed from \(oldValue)")
        }
    }
}

var player = Player(score: 50)
player.score = 75
// Output:
// Score will change to 75
// Score changed from 50

Static Properties

Belong to the type, not instances.

Example:

swift
struct Settings {
    static let defaultTheme = "Light"
    static var currentTheme = defaultTheme
}

print(Settings.currentTheme) // "Light"
Settings.currentTheme = "Dark"

Property Initialization

  • Stored properties must be initialized before use.
  • Use default values, init, or lazy initialization.

Example: Initialization:

swift
struct User {
    let id: String
    var name: String = "Anonymous"
    
    init(id: String) {
        self.id = id
    }
}

Property Wrappers

Encapsulate property behavior (covered in PropertyWrappers.md).

Example Preview:

swift
@propertyWrapper
struct Clamped {
    var wrappedValue: Int
}

struct Game {
    @Clamped var score: Int
}

Thread Safety

Properties accessed concurrently require synchronization (e.g., actors, locks).

Example with Actor:

swift
actor Scoreboard {
    var score: Int = 0
    func updateScore(by points: Int) {
        score += points
    }
}

Best Practices

  • Prefer let: Use constants to prevent unintended changes.
  • Descriptive Names: Use clear names (e.g., userName vs. n).
  • Lazy Initialization: Use lazy for resource-intensive properties.
  • Computed Properties: Use for derived values to avoid storing redundant data.
  • Observers: Use sparingly to avoid complex side effects.
  • Type Inference: Leverage inference but annotate when clarity is needed.
  • Static Properties: Use for type-wide constants or shared state.

Troubleshooting

  • Immutable Error: Ensure var is used for mutable properties.
  • Uninitialized Property: Provide default values or initialize in init.
  • Thread Safety Issues: Use actors or dispatch queues for concurrent access.
  • Lazy Property Crash: Ensure self is available when accessing lazy properties.
  • Observer Loops: Avoid recursive updates in didSet or willSet.

Example: Comprehensive Property Usage

swift
struct TemperatureSensor {
    static let maxReading = 100.0 // Static constant
    private var readings: [Double] // Stored property
    lazy var averageReading: Double = { // Lazy computed
        readings.reduce(0, +) / Double(readings.count)
    }()
    
    var latestReading: Double { // Computed property
        get { readings.last ?? 0.0 }
        set {
            if newValue <= Self.maxReading {
                readings.append(newValue)
            }
        }
    }
    
    var readingCount: Int { // Read-only computed
        readings.count
    }
    
    init(initialReading: Double) {
        self.readings = [initialReading]
    }
}

var sensor = TemperatureSensor(initialReading: 25.0)
sensor.latestReading = 30.0
print(sensor.readingCount) // 2
print(sensor.averageReading) // 27.5

Released under the MIT License.