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
15: Structs vs Classes
A few years ago, I was reviewing a PR for a junior dev who was building a shopping cart system. They had created a CartItem as a class. It seemed logical—a cart item is a "thing," right? But they hit a weird bug: when they updated the quantity of an item in the cart, that same item's quantity was mysteriously updating in the "Recommended Products" list on the sidebar. They were scratching their heads for hours until we realized that both the cart and the recommendation engine were holding a reference to the exact same instance of that class. By changing the quantity in one place, they had mutated the object everywhere it existed in memory.
This is the quintessential "aha!" moment for understanding the difference between structs and classes in Swift. It comes down to how the computer handles the data: Value types versus Reference types.
The Independence of Value Types
Structs are value types. When you assign a struct to a new variable or pass it into a function, Swift creates a unique copy of that data. I like to think of this like sending someone a photocopy of a document; if they scribble on their copy, your original remains pristine.
struct Point {
var x: Double
var y: Double
}
var pointA = Point(x: 10, y: 10)
var pointB = pointA // A copy is made here
pointB.x = 50
print(pointA.x) // Still 10
print(pointB.x) // Now 50
Because structs are copied, they are incredibly safe. You don't have to worry about some distant piece of code changing your data behind your back. This makes your code much easier to reason about, especially when you start dealing with asynchronous tasks or multi-threaded code.
Shared State and Reference Types
Classes are reference types. When you assign a class instance to a new variable, you aren't copying the data itself; you're copying the reference (the memory address) to that data. Now, both variables are pointing to the same spot in memory. Using the document analogy, this is like sharing a Google Doc; if one person deletes a paragraph, everyone sees the change immediately.
class UserProfile {
var username: String
init(username: String) {
self.username = username
}
}
let userA = UserProfile(username: "SwiftCoder")
let userB = userA // Both point to the same instance
userB.username = "iOSMaster"
print(userA.username) // "iOSMaster" - it changed for both!
Reference types are powerful when you actually want shared state. For instance, if you have a DatabaseManager or a SessionManager, you generally want every part of your app to talk to the same single instance, not a bunch of disconnected copies.
Choosing the Right Tool for the Job
You'll hear people say "just use structs for everything," and honestly? They're mostly right. In modern Swift development, the default choice should almost always be a struct. They are faster to allocate and eliminate an entire category of bugs related to shared state.
So, when do you actually reach for a class? I use a simple rule of thumb: Identity vs. Value. If two instances are "equal" because they have the same properties (like two Coordinate objects at 10,10), use a struct. If two instances are only the same if they are the exact same object in memory (like two User objects that happen to have the same name but are different people), use a class.
Also, remember that classes give you inheritance (the ability to subclass), while structs do not. If you find yourself needing a complex hierarchy of types, a class might be necessary, though I'd encourage you to look into Protocols first—they usually provide a cleaner way to share behavior without the baggage of class inheritance.
📋 Practical Task
Fixing the Shared Scoreboard Bug
You are building a competitive quiz app. Currently, the PlayerScore is implemented as a class. Because of this, when the app creates a "Guest" player and a "User" player, it accidentally assigns them the same reference to a score object. When the Guest earns a point, the User's score increases too!
Your Task:
- Review the code below.
- Convert the
PlayerScorefrom aclassto astruct. - Adjust the
initmethod (remember that structs provide a memberwise initializer automatically, so you can simplify or remove the explicit init). - Verify that changing
guestScore.valueno longer affectsuserScore.value.
class PlayerScore {
var value: Int
init(value: Int) {
self.value = value
}
}
var userScore = PlayerScore(value: 0)
var guestScore = userScore // Currently sharing a reference!
guestScore.value = 10
print("User Score: \(userScore.value)") // Currently prints 10
print("Guest Score: \(guestScore.value)") // Currently prints 10
There are no comments for now.