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
16: Properties and Methods
I want to start today with a snippet of code that I've seen trip up almost every developer I've mentored when they first dive into Swift structs. You’re building a simple bank account tracker, and you’ve got a method to add money. It looks perfectly logical, but when you hit build, Xcode screams at you.
struct BankAccount {
var balance: Double = 0.0
func deposit(amount: Double) {
balance += amount // ❌ Error: Cannot assign to property: 'self' is immutable
}
}
The "Cannot assign to property" Headache
At first glance, this looks wrong. You defined `balance` as a `var`, so it should be changeable, right? The problem isn't the variable; it's the nature of the struct itself. In Swift, structs are value types. By default, the methods inside a struct aren't allowed to modify the properties of that struct because the instance itself is considered immutable when passed around.
If you were using a class, this would work fine. But with a struct, Swift forces you to be explicit about which methods change the internal state. This is actually a huge win for safety—it prevents you from accidentally changing data in a place where you thought you were just reading it.
Adding the Mutating Keyword
To fix this, we use the mutating keyword. This tells the compiler, "Hey, this specific method is going to change the data inside this struct."
struct BankAccount {
var balance: Double = 0.0
mutating func deposit(amount: Double) {
balance += amount // ✅ Now this works!
}
}
Now, the compiler is happy. I usually suggest thinking of mutating as a warning label for other developers reading your code. It signals that calling this method will change the object's state.
Logic Without Storage
Now that we can change data, let's talk about how we read it. Most of the time, you'll use stored properties (like balance), which just hold a value. But sometimes you need a property that calculates something on the fly. That's where computed properties come in.
Imagine you want to know if an account is overdrawn. You could create a stored property for isOverdrawn, but then you'd have to remember to update it every single time the balance changes. That's a recipe for bugs. Instead, do this:
struct BankAccount {
var balance: Double = 0.0
// This is a computed property
var isOverdrawn: Bool {
return balance < 0
}
mutating func deposit(amount: Double) {
balance += amount
}
}
Notice there's no = sign after isOverdrawn. It doesn't store a value in memory; it just runs that block of code every time you ask for the value. It's clean, it's efficient, and it's always accurate.
Organizing Behavior with Methods
Methods are essentially the "verbs" of your object. While properties describe what the object is, methods describe what the object does. A good rule of thumb I follow is: if the logic requires more than a simple calculation or a true/false check, it belongs in a method, not a computed property.
For example, withdrawing money isn't just about subtraction; it's about checking if you have enough funds first. That's a behavior, so it's a perfect candidate for a method:
struct BankAccount {
var balance: Double = 0.0
var isOverdrawn: Bool {
return balance < 0
}
mutating func deposit(amount: Double) {
balance += amount
}
mutating func withdraw(amount: Double) -> Bool {
if balance >= amount {
balance -= amount
return true // Success
} else {
return false // Insufficient funds
}
}
}
By grouping this logic inside the BankAccount struct, you've ensured that no matter where this struct is used in your app, the rules for withdrawing money are always enforced in one single place.
📋 Practical Task
Building a SmartLight Controller
Your task is to create a SmartLight struct that manages the state of a smart bulb. This will exercise your ability to use stored properties, computed properties, and mutating methods.
Requirements:
- Stored Properties:
brightness(an Integer from 0 to 100)isOn(a Boolean)
- Computed Property:
statusDescription: Returns a string. If the light is off, return "The light is off". If it's on and brightness is 0, return "The light is on but dim". Otherwise, return "The light is on at \(brightness)%".
- Mutating Methods:
togglePower(): Flips theisOnvalue.setBrightness(to level: Int): Updates thebrightnessproperty, but ensures the value stays between 0 and 100 (cap it at 100 if the input is higher, and floor it at 0 if the input is lower).
Testing your code: Create an instance of SmartLight, call setBrightness(to: 150), toggle the power, and print the statusDescription to the console to verify everything works as expected.
There are no comments for now.