Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
17: Computed Properties and Property Observers
I see this all the time when I'm reviewing PRs from developers moving into Swift: they try to attach a didSet observer to a computed property. They expect the observer to fire whenever the computed value "changes."
// This will NOT compile
var totalScore: Int {
return kills * 100 + assists * 50
}
didSet {
print("Score updated!") // Compiler Error: Property observers cannot be added to computed properties
}
Here is why that's a fundamental misunderstanding: a computed property doesn't have a value to change. It's actually just a pair of functions (a getter and a setter) disguised as a variable. When you access totalScore, Swift isn't looking up a value in memory; it's running a calculation. Since there is no actual storage being modified, there is nothing for an observer to "watch."
Computed Properties are Functions in Disguise
Think of a computed property as a way to provide a clean interface for a value that is derived from other data. I use these constantly to avoid "state synchronization" bugs—where you have two variables that should always be in sync, but one gets updated while the other is forgotten.
Let's look at a Rectangle. Instead of storing the area and updating it every time the width or height changes, we compute it on the fly:
struct Rectangle {
var width: Double
var height: Double
var area: Double {
return width * height
}
}
In this case, area is a read-only computed property. But what if we want to be able to change the area? We can provide a set block. Inside the setter, Swift provides a default constant called newValue, which is the value the user is trying to assign to the property.
struct Rectangle {
var width: Double
var height: Double
var area: Double {
get {
return width * height
}
set {
// We'll keep the width constant and adjust the height to fit the new area
height = newValue / width
}
}
}
I'll give you a pro tip here: use computed properties whenever a value can be derived from existing state. If you can calculate it, don't store it. It saves memory and prevents your data from getting out of sync.
Property Observers Watch the Storage, Not the Calculation
Now, if we actually want to perform an action when a value changes—like updating a UI label or logging a change to a database—we use Property Observers: willSet and didSet. Unlike computed properties, these must be attached to stored properties.
The observer doesn't replace the setter; it augments it. It's like a hook that runs right before or right after the value in memory is swapped.
class UserProfile {
var username: String {
willSet {
print("About to change name to \(newValue)")
}
didSet {
print("Changed name from \(oldValue) to \(username)")
}
}
init(username: String) {
self.username = username
}
}
Note the difference in naming: willSet gives you newValue (what's coming), and didSet gives you oldValue (what just left). I find didSet to be the workhorse here; it's incredibly useful for triggering a "refresh" logic whenever a specific piece of state changes.
Just be careful: if you accidentally update the stored property inside its own didSet, you'll create an infinite loop and crash your app. Swift has some protections against this in newer versions, but it's still a logic trap to avoid.
📋 Practical Task
Exercise: Implementing a Game Character Stat Tracker
You are building a character sheet for an RPG. The character has a strength score and an agility score. You need to implement the following logic:
- Create a
Characterclass with stored properties forstrengthandagility. - Create a computed property called
combatPowerthat returns the sum of strength and agility multiplied by 1.5. - Add a property observer to
strengthso that whenever it is updated, it prints:"Strength changed from [oldValue] to [newValue]!" - Add a property observer to
agilitythat prints a warning"Agility is too low!"only if the new value is below 5.
Test your implementation by creating a character, changing their strength, and dropping their agility below 5 to ensure the observers trigger correctly.
There are no comments for now.