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:
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:
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:
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:
let count = 42 // Inferred as Int
let price = 19.99 // Inferred as Double
let names = ["Alice", "Bob"] // Inferred as [String]
Ambiguity Resolution:
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:
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:
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:
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:
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:
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:
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:
@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:
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 accessinglazy
properties. - Observer Loops: Avoid recursive updates in
didSet
orwillSet
.
Example: Comprehensive Property Usage
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