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
184: Opaque Types with some Keyword
You've likely seen the some keyword popping up everywhere if you've spent any time with SwiftUI, but it's often glossed over as just "some magic that makes the code compile." In reality, it's a powerful tool for API design called Opaque Types. The goal is simple: I want to tell you that a function returns something that conforms to a protocol, but I want to keep the actual concrete type a secret.
Defining our Drawable interface
Let's imagine we're building a simple graphics engine. We have a protocol called Drawable. Any object that can be rendered to the screen should follow this contract. I'll start by setting up the protocol and a couple of concrete shapes.
protocol Drawable {
func draw() -> String
}
struct Circle: Drawable {
func draw() -> String { "Drawing a Circle ◯" }
}
struct Square: Drawable {
func draw() -> String { "Drawing a Square □" }
}
Now, I want a function that provides a default shape for the user. At first glance, you might think, "Why not just return Drawable?" Let's look at why some Drawable is different.
Hiding the implementation with some
If I return Drawable (the protocol itself), I'm using an existential type. That's fine for some cases, but it erases the specific type identity. By using some Drawable, I'm telling the Swift compiler: "This function returns one specific type that conforms to Drawable, but the caller doesn't need to know which one it is."
func makeDefaultShape() -> some Drawable {
return Circle()
}
The beauty here is that the caller knows the result is Drawable, but they can't treat it as a Circle. If I decide to change the default shape to a Square tomorrow, I don't have to change a single line of code in the rest of the app. I've successfully decoupled the implementation from the interface.
The "One Type" trap
Here is where I usually trip up when I'm first implementing this, and it's a mistake you'll likely make too. I thought, "Since I'm hiding the type anyway, I can return different shapes based on a condition!" Let's try this:
func makeShape(isRound: Bool) -> some Drawable {
if isRound {
return Circle()
} else {
return Square()
}
}
If you try to compile this, Swift will scream at you. You'll get an error saying "Function declares an opaque return type, but the return statements in its body do not have the same underlying type!"
I forgot the golden rule of some: while the caller doesn't know the type, the compiler does. An opaque return type must be one specific type for every possible execution path. You can't switch between a Circle and a Square because the compiler needs to guarantee the identity of that type for the duration of the program's execution.
Keeping the type identity intact
To fix this, I have two choices. I could go back to using the protocol as the return type (existential), but if I want to keep the performance and type-safety benefits of opaque types, I need to wrap my logic. I'll create a wrapper type that conforms to Drawable and handles the switching internally.
struct ShapeWrapper: Drawable {
let isRound: Bool
func draw() -> String {
return isRound ? Circle().draw() : Square().draw()
}
}
func makeShape(isRound: Bool) -> some Drawable {
// Now we are returning exactly ONE type: ShapeWrapper
return ShapeWrapper(isRound: isRound)
}
By doing this, I've satisfied the compiler. The return type is now consistently ShapeWrapper, but the user of the function still just sees some Drawable. This is exactly how SwiftUI's View works—it wraps a massive tree of different types into one single opaque some View.
📋 Practical Task
Build a Theme-Based Component Provider
You are building a UI library that supports different themes. Create a protocol named Themeable with a property primaryColor (a String). Create two concrete structs, LightTheme and DarkTheme, that conform to this protocol.
Your task is to write a function called provideTheme(isDarkMode: Bool) that returns some Themeable. To avoid the "One Type" trap we discussed in the lesson, you must implement a ThemeWrapper struct that handles the conditional logic internally. Ensure that the function returns the wrapper, maintaining the opaque return type.
There are no comments for now.