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
37: Sequence and Collection Protocols
I was working on a project recently where I needed a custom wrapper around a set of data—essentially a Playlist object—and I realized I was doing a lot of tedious work. I found myself writing methods like getSong(at: Int) and allSongs() -> [Song] just to make the data accessible. It felt clunky. In Swift, if your object behaves like a list of things, it should be a list of things.
Why can't I just loop through this?
Let's start with a basic setup. I've got a Playlist struct that holds an array of song titles. Simple enough.
struct Playlist {
var songs: [String]
}
let myPlaylist = Playlist(songs: ["Blue Monday", "Song 2", "Hysteria"])
// I want to do this:
for song in myPlaylist {
print(song)
}
If you try to compile this, the compiler will scream at you. It'll say "Type 'Playlist' does not conform to protocol 'Sequence'". This is our first clue. The for-in loop doesn't actually care if your object is an Array; it only cares if your object conforms to the Sequence protocol.
Giving it a pulse with Sequence
The Sequence protocol is the most basic requirement for iteration. To make it work, I just need to provide a way to create an iterator. Now, I could write my own iterator from scratch, but since I'm already wrapping an array, I can just "cheat" and use the array's own iterator.
extension Playlist: Sequence { func makeIterator() -> IndexingIterator<[String]> { return songs.makeIterator() } } // Now this works! for song in myPlaylist { print(song) }That's great for looping, but here is where I hit a wall. I tried to access a specific song using a subscript—something we do a thousand times a day with arrays—and it failed.
let secondSong = myPlaylist[1] // Error: Value of type 'Playlist' has no subscriptThis is the fundamental distinction in Swift: a
Sequenceis just something you can move through from start to finish. It doesn't guarantee that you can go back, that you know how many items are in it, or that you can jump to a specific spot. It's like a stream of data; once you've iterated through it, it might be gone.The frustration of missing indices
If I want my
Playlistto feel like a first-class citizen in Swift—meaning I can usecount,first,last, and subscripts—I need to move up to theCollectionprotocol.Collectioninherits fromSequence, but it adds a lot of requirements. It demands that we define what anIndexis and how to move between those indices.I'll try to add
Collectionconformance now. This is where it gets slightly more verbose because we have to be explicit about the boundaries of our collection.extension Playlist: Collection { // We tell Swift that our index is just an Integer typealias Index = Int var startIndex: Index { return songs.startIndex } var endIndex: Index { return songs.endIndex } func index(after i: Index) -> Index { return songs.index(after: i) } subscript(position: Index) -> String { return songs[position] } }Now, look at what happens. Because I've conformed to
Collection, I didn't have to write acountproperty or afirstproperty. Swift provides those for free based on thestartIndex,endIndex, and theindex(after:)method. I've essentially told Swift: "Here is how you start, here is how you stop, and here is how you get to the next item."Putting it to the test
Let's see if this actually behaves the way I expect. I can now treat my custom struct as if it were an array, but I still maintain the encapsulation of the
Playlisttype.let myPlaylist = Playlist(songs: ["Blue Monday", "Song 2", "Hysteria"]) print(myPlaylist.count) // 3 (Automatic!) print(myPlaylist[1]) // "Song 2" (Via our subscript) print(myPlaylist.first!) // "Blue Monday" (Automatic!) for (index, song) in myPlaylist.enumerated() { print("\(index): \(song)") }The beauty of this approach is that any generic function in Swift that expects a
Collectionwill now accept myPlaylist. I'm no longer fighting the language; I'm using its protocols to define the behavior of my data.
📋 Practical Task
Implementing a CoordinateGrid Collection
You are building a game engine and have a CoordinateGrid struct that stores a flat array of tiles, but it represents a 2D grid. Instead of making the user manage a 1D array, you want the CoordinateGrid itself to be a Collection so it can be iterated over and accessed via subscripts.
Your Task:
- Create a struct
CoordinateGridthat contains an internal array of strings calledtiles. - Implement the
Collectionprotocol forCoordinateGrid. - Ensure that you define the
Index,startIndex,endIndex,index(after:), and thesubscript. - In your test code, instantiate a grid with 5 tiles, print the
countof the grid, and use afor-inloop to print each tile.
There are no comments for now.