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
108: Subscripts in Swift
I remember the first time I tried to build a coordinate-based system for a small strategy game in Swift. I had a class to manage the grid, and inside that class, I had a 2D array holding the map data. It seemed obvious that I should be able to access a tile using square brackets, like map[x, y]. But when I tried, the compiler hit me with a wall of red: "Value of type 'GameMap' cannot be subscripted."
struct GameMap {
var grid = [["Forest", "Forest", "Mountain"],
["Forest", "Water", "Forest"],
["Mountain", "Forest", "Forest"]]
}
var myMap = GameMap()
// This is what I wanted to do:
let tile = myMap[1, 1] // ❌ Error: Value of type 'GameMap' cannot be subscripted
The missing link between the class and the array
The mistake here is a common assumption: thinking that if a class contains a collection, the class itself automatically inherits the ability to be subscripted. It doesn't. Swift is very explicit about this. The grid property is subscriptable because it's an Array, but myMap is an instance of GameMap, and until you tell Swift how to handle square brackets for that specific type, it has no idea what you're trying to do.
You could "fix" this by accessing the property directly—myMap.grid[1][1]—but that's leaky abstraction. If you later decide to change your internal storage from a 2D array to a dictionary or a flat array for performance reasons, you'd have to hunt down every single .grid[x][y] call in your entire codebase and change it.
Mapping indices with the subscript keyword
To fix this properly, we use the subscript keyword. This allows us to define a custom getter (and optionally a setter) that triggers whenever square brackets are used on an instance of the type. The coolest part? Subscripts can take multiple arguments, which is perfect for grids.
struct GameMap {
var grid = [["Forest", "Forest", "Mountain"],
["Forest", "Water", "Forest"],
["Mountain", "Forest", "Forest"]]
// We define the subscript here
subscript(row: Int, col: Int) -> String {
get {
return grid[row][col]
}
set {
grid[row][col] = newValue
}
}
}
var myMap = GameMap()
print(myMap[1, 1]) // ✅ Prints "Water"
myMap[0, 0] = "City" // ✅ Updates the grid via the setter
By adding that block, we've created a shortcut. Now, myMap[1, 1] is actually just a syntactic sugar for calling a function we defined. I personally love this approach because it keeps the "how" (the internal array) hidden and the "what" (getting a tile at a coordinate) clean.
Handling the "Out of Bounds" crash
If you use the code above and call myMap[10, 10], your app will crash. That's because we're passing the index straight to the internal array, which throws a fatal error if the index is out of range. When you write your own subscripts, you have the power to make them safer than standard arrays.
You can make a subscript return an optional, allowing you to handle missing data gracefully instead of crashing the program. Here is how I usually handle this in production code:
subscript(row: Int, col: Int) -> String? {
get {
// Check if the row exists first
guard row >= 0 && row < grid.count else { return nil }
// Then check if the column exists in that row
guard col >= 0 && col < grid[row].count else { return nil }
return grid[row][col]
}
set {
// Only set the value if the coordinates are actually valid
if row >= 0 && row < grid.count && col >= 0 && col < grid[row].count {
grid[row][col] = newValue ?? "Unknown"
}
}
}
Now, myMap[10, 10] simply returns nil. It's a small change, but it's the difference between a professional tool and a buggy prototype.
📋 Practical Task
Building a Safe Configuration Manager
Imagine you are building a settings system for an app. You want to be able to access configuration values using a string key (like config["theme"]), but you want the subscript to return a default value if the key doesn't exist, rather than returning an optional or crashing.
Your Task: Create a struct called AppConfig that meets these requirements:
- It should have a private dictionary called
settingsthat storesStringkeys andStringvalues. - Implement a read-only subscript that takes a
String. - If the key exists in the dictionary, return the value.
- If the key does not exist, return the string
"Not Set".
Test your code with this:
let config = AppConfig()
print(config["api_url"]) // Should print "Not Set"
There are no comments for now.