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. | |
| read-only computed property | String |
A computed property with only a getter, making it read-only. | |
| read-write computed property | Double |
A computed property with both getter and setter. | |
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. | |
| @ObservedObject | Used in SwiftUI to observe changes in observable objects. | |
| @StateObject | Initializes and manages a state object in SwiftUI. | |
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 | class |
Defines a type property that can be overridden by subclasses. | |
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).