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
185: Existential Types with any Keyword Revisited
I remember working with a junior dev a few years back who was building a dashboard for a fintech app. He had a Widget protocol and several structs—StockTicker, AccountBalance, and NewsFeed—that all conformed to it. He tried to create a list of widgets for the home screen using var widgets: [some Widget] = []. He was baffled when the compiler screamed at him, because he knew every item he was adding was, indeed, a Widget. He didn't realize that by using some, he was telling Swift that the array contained one specific (though hidden) type, rather than a mixture of different types. He wanted a bag of widgets, but he had accidentally asked for a bag of identical clones.
The Distinction Between a Specific Type and a Box
This is where we revisit the any keyword. When you use some Widget, you're dealing with an opaque type. The compiler knows exactly what the type is at compile time; it's just hiding that detail from you. But when you use any Widget, you're creating an existential type. Think of any as a physical box. Inside that box is some instance that conforms to Widget, but the box itself is the type. Because the box is the same regardless of what's inside, you can put a StockTicker box and a NewsFeed box into the same array.
protocol Widget {
func render() -> String
}
struct StockTicker: Widget {
func render() -> String { "AAPL: $150" }
}
struct NewsFeed: Widget {
func render() -> String { "Swift 6 is coming!" }
}
// This works because 'any' allows heterogeneity
let dashboard: [any Widget] = [StockTicker(), NewsFeed()]
I'll be honest: in the early days of Swift, we just used the protocol name itself (e.g., [Widget]), and the compiler implicitly treated it as an existential. But that caused a lot of confusion between generics and existentials. Adding the any keyword makes it explicit that you are opting into "type erasure." You're telling the compiler, "I don't care about the specific underlying type anymore; just give me the protocol interface."
The Cost of the Existential Box
You might wonder why we don't just use any everywhere to make things easier. There's a price to pay. Every time you wrap a value in any, Swift has to manage a "Widget Table" (similar to a vtable) to keep track of the underlying type's methods. This adds a small amount of overhead. More importantly, you lose the ability to use Self or associatedtype requirements in a straightforward way. If your protocol has an associated type, using it as an any type can get messy because the compiler no longer knows which specific version of that associated type is inside the box.
I generally advise my team to stick with some (generics) whenever possible. If you're passing a dependency into a function, use some. If you're returning a specific type but want to hide the implementation, use some. Only reach for any when you truly need a collection of different types or when the type of an object needs to change dynamically at runtime.
Handling Existentials in Logic
Once you've stored something as any Widget, you can call any method defined in the Widget protocol. However, if you suddenly need to access a property that only exists on StockTicker, you have to "unbox" it using a conditional cast. It's a bit like reaching into the box and checking if the object inside is what you think it is.
for widget in dashboard {
print(widget.render()) // Works: render() is in the protocol
if let ticker = widget as? StockTicker {
print("This is specifically a stock ticker!")
}
}
📋 Practical Task
Implementing a Heterogeneous Notification System
You are building a notification system where different types of alerts (Email, SMS, and Push) must be processed in a single queue. Currently, the system is failing because it tries to use a generic constraint where an existential type is required.
Your Task:
- Define a protocol
NotificationAlertwith a methodsend()that returns aString. - Create three structs—
EmailAlert,SMSAlert, andPushAlert—that conform to this protocol, each returning a unique string (e.g., "Sending Email...", etc.). - Create a class called
NotificationManager. Inside this class, implement a property calledqueuethat can hold a mixture of anyNotificationAlert. - Implement a method
processAll()in the manager that iterates through the queue and prints the result ofsend()for every alert. - Instantiate the manager, add one of each alert type to the queue, and call
processAll().
There are no comments for now.