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
19: Protocols and Protocol Extensions
I once worked with a developer who was trying to build a complex payment system. He started with a massive base class called PaymentMethod, and every specific type—CreditCard, ApplePay, PayPal—inherited from it. It worked fine until we needed to add a "refund" feature. The problem? Only some payment methods were refundable. He ended up putting a refund() method in the base class that just threw an error or did nothing for non-refundable types. It was a mess. He was fighting the inheritance hierarchy because he was trying to force a "is-a" relationship when he actually needed a "can-do" relationship.
That's where protocols come in. Instead of saying a CreditCard is a PaymentMethod, we say it conforms to a protocol. It’s a shift in mindset from inheritance to composition, and in Swift, it is arguably the most powerful tool in your architectural kit.
Defining Capabilities with Protocols
A protocol is essentially a contract. It doesn't implement any logic itself; it just tells the compiler, "I don't care what this object actually is, as long as it has these specific properties and methods." This allows you to write code that is incredibly flexible. If you have a function that needs to process a payment, it shouldn't care if it's a credit card or a digital wallet—it only cares that the object conforms to a PaymentMethod protocol.
protocol PaymentMethod {
var providerName: String { get }
func processPayment(amount: Double)
}
protocol Refundable {
func refund(amount: Double)
}
struct CreditCard: PaymentMethod, Refundable {
let providerName = "Visa"
func processPayment(amount: Double) {
print("Charging $\(amount) to Visa card.")
}
func refund(amount: Double) {
print("Refunding $\(amount) back to Visa card.")
}
}
struct GiftCard: PaymentMethod {
let providerName = "Store Credit"
func processPayment(amount: Double) {
print("Deducting $\(amount) from gift card balance.")
}
// Gift cards aren't refundable in our system, so we just don't conform to Refundable.
}
Notice how GiftCard doesn't have to implement a fake refund method. It simply doesn't opt into the Refundable contract. This keeps your types lean and your logic honest.
Providing Default Logic via Protocol Extensions
Now, here is where things get interesting. If you find yourself writing the exact same helper method in five different structs that all conform to the same protocol, you're duplicating code. In many languages, you'd be forced back into using a base class. But in Swift, we have protocol extensions.
A protocol extension allows you to provide a default implementation for a protocol method. Any type that conforms to the protocol automatically gets this behavior, but they can still override it if they need something more specific. I use this constantly to create "standard" behavior that can be customized on a case-by-case basis.
extension PaymentMethod {
func printReceipt(amount: Double) {
print("Receipt: Paid $\(amount) using \(providerName). Thank you!")
}
}
// Now, both CreditCard and GiftCard have printReceipt()
// without us having to write it inside the structs.
let myCard = CreditCard()
myCard.printReceipt(amount: 50.0)
This is a game changer for API design. You can define a protocol with a few required properties, and then use extensions to build a whole library of functionality on top of those properties. You're essentially adding capabilities to any type that fits the blueprint, regardless of where that type sits in your class hierarchy.
📋 Practical Task
Building a Smart Home Device Controller
You are tasked with building a system to manage various smart home devices. Not all devices behave the same way: some can be toggled on and off, while others (like smart bulbs) can also be dimmed.
Your Requirements:
- Create a protocol called
Powerablethat requires a boolean propertyisOnand a methodtogglePower(). - Create a protocol called
Dimmablethat requires a propertybrightness(a Double from 0.0 to 1.0) and a methodsetBrightness(level: Double). - Implement a protocol extension for
Powerablethat adds a methodstatusReport()which prints whether the device is currently "On" or "Off". - Create a
SmartLightstruct that conforms to bothPowerableandDimmable. - Create a
SmartPlugstruct that conforms only toPowerable.
Testing your code: Create an array of Powerable objects containing both a light and a plug. Loop through them and call statusReport() on each to verify that the protocol extension is working across different types.
There are no comments for now.