-
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
61: Practice Exercise: Refactoring Class-Based Code into Value Types
Imagine you and a colleague are collaborating on a project. If you both have a link to the same Google Doc, you're dealing with a reference. When your colleague deletes a paragraph, it disappears for you too, instantly. That's how classes work in Swift. Now, imagine instead that you emailed them a PDF version of that document. They can highlight it, scribble in the margins, or even delete the whole thing, and your original file remains untouched. That's a value type, like a struct.
In a large codebase, the "Google Doc" approach (classes) can be a nightmare. You pass a user object to a function, that function changes the user's name for some internal reason, and suddenly your UI is displaying the wrong name elsewhere because every part of your app was pointing to that one single instance. I've spent more hours than I'd like to admit hunting down bugs caused by this kind of shared state.
The Danger of the Shared Document
Let's look at a typical mistake. Suppose we have a PlayerCharacter defined as a class. Because it's a reference type, assigning it to a new variable doesn't create a new character; it just creates another pointer to the same person.
class PlayerCharacter {
var name: String
var health: Int
init(name: String, health: Int) {
self.name = name
self.health = health
}
}
var hero = PlayerCharacter(name: "Arin", health: 100)
var backupHero = hero // This is NOT a copy. It's a reference.
backupHero.health = 50
print(hero.health) // Output: 50. Wait, what?
You probably intended for backupHero to be a snapshot of the hero's state, but instead, you accidentally damaged the original. This is where the "Value Type" mindset saves you.
Tearing Down the Reference
To fix this, we refactor the class into a struct. But it's not as simple as just changing the word class to struct. You have to think about how the data changes. Since structs are value types, any method that modifies a property must be marked with the mutating keyword. This tells Swift that the method will replace the old value with a new one.
struct PlayerCharacter {
var name: String
var health: Int
// We don't need the explicit init here; Swift gives us a memberwise one for free.
mutating func takeDamage(_ amount: Int) {
health -= amount
}
}
var hero = PlayerCharacter(name: "Arin", health: 100)
var backupHero = hero // Now this is a genuine copy.
backupHero.takeDamage(50)
print(hero.health) // Output: 100. Perfect.
Notice how the behavior changed? By switching to a struct, we've ensured that hero and backupHero are completely independent. If you change one, the other doesn't care.
When to Stick With the Refactor
I'll be honest: there are times when you actually want a class (like when you're managing a single, shared database connection or a hardware sensor). But for data models—things like Users, Settings, Coordinates, or Game Stats—structs should be your default. They are faster, they are thread-safe by nature, and they eliminate the "who changed my variable?" mystery that plagues reference-heavy code.
📋 Practical Task
Exercise: Converting a Shared Shopping Cart to an Independent Value Type
You've been handed a piece of legacy code where a ShoppingCart is implemented as a class. This is causing bugs because when a user "clones" their cart to compare items, changing the clone is accidentally modifying the original cart.
Your Task:
- Refactor the
ShoppingCartclass into astruct. - Remove the manual
init(let Swift's memberwise initializer handle it). - Mark the
addItemandclearCartmethods asmutatingso the code compiles. - Test your refactor by creating a cart, assigning it to a second variable, adding an item to the second variable, and verifying that the first cart remains empty.
// STARTING CODE
class ShoppingCart {
var items: [String]
init(items: [String]) {
self.items = items
}
func addItem(_ item: String) {
items.append(item)
}
func clearCart() {
items.removeAll()
}
}
var mainCart = ShoppingCart(items: [])
var comparisonCart = mainCart
comparisonCart.addItem("Mechanical Keyboard")
print("Main Cart: \(mainCart.items)") // Currently prints ["Mechanical Keyboard"]
print("Comparison Cart: \(comparisonCart.items)") // Currently prints ["Mechanical Keyboard"]
There are no comments for now.