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
18: Inheritance and Polymorphism
Inheritance and polymorphism sound like academic buzzwords, but in the real world, they're just tools to stop you from writing the same code ten times. I like to think of inheritance as "is-a" relationship: a Credit Card payment is a Payment Method. Polymorphism is the magic that lets us treat a whole group of different objects as if they were the same thing, while still letting each one keep its own unique behavior.
Defining the shared blueprint
Let's build a simple payment processing system. If we have five different ways to pay, we don't want to write five different functions to handle them. Instead, I'll start by creating a base class that holds everything every payment method needs—like the amount.
class PaymentMethod {
var amount: Double
init(amount: Double) {
self.amount = amount
}
func processPayment() {
print("Processing a generic payment of $\(amount)")
}
}
This is our foundation. Any class that inherits from PaymentMethod will automatically have an amount and a processPayment method. It's a way of saying, "I don't care how you do it, but if you want to be a PaymentMethod, you must be able to process a payment."
Specializing the payment types
Now, a Credit Card payment needs a card number, and a PayPal payment needs an email address. I'll create subclasses for these. I'll use the override keyword to change how processPayment works for each one.
class CreditCardPayment: PaymentMethod {
var cardNumber: String
init(amount: Double, cardNumber: String) {
self.cardNumber = cardNumber
super.init(amount: amount)
}
override func processPayment() {
print("Charging $\(amount) to card \(cardNumber)... Success!")
}
}
class PayPalPayment: PaymentMethod {
var email: String
init(amount: Double, email: String) {
self.email = email
super.init(amount: amount)
}
override func processPayment() {
print("Redirecting to PayPal for \(email) to pay $\(amount)... Success!")
}
}
Notice the super.init call. In Swift, you have to initialize your own properties before you can tell the parent class to initialize its properties. It's a strict rule, but it prevents the app from crashing because of uninitialized data.
The "Wait, why isn't this compiling?" moment
Here is where I usually trip up when I'm moving fast. I decided to put all my payments into a single array so I could process them in one loop. I tried to do something like this:
let myPayments: [PaymentMethod] = [
CreditCardPayment(amount: 50.0, cardNumber: "1234-5678"),
PayPalPayment(amount: 25.0, email: "dev@example.com")
]
for payment in myPayments {
// I tried to access the card number here...
print(payment.cardNumber)
}
The compiler immediately yelled at me. Why? Because the myPayments array is typed as [PaymentMethod]. The compiler only knows that every item in that list is a PaymentMethod, and PaymentMethod doesn't have a cardNumber property. Only the CreditCardPayment subclass has that.
The mistake was trying to treat a generic parent as a specific child. The fix is to rely on the shared interface—the method they both have in common.
Treating everything as a PaymentMethod
This is where polymorphism actually shines. Instead of trying to access specific properties, I just call the method that exists on the base class. Swift is smart enough to know which version of the method to run based on the actual type of the object at runtime.
let myPayments: [PaymentMethod] = [
CreditCardPayment(amount: 50.0, cardNumber: "1234-5678"),
PayPalPayment(amount: 25.0, email: "dev@example.com"),
PaymentMethod(amount: 10.0) // A generic payment
]
for payment in myPayments {
// This is polymorphism!
// One call, three different behaviors.
payment.processPayment()
}
If you run this, you'll see the card-specific message for the first item, the PayPal message for the second, and the generic message for the third. I didn't have to write a single if statement or switch case to check the type. The objects just "know" how to behave.
📋 Practical Task
Building a Multimedia Notification System
You need to build a system that sends notifications to users via different channels. Some users want Emails, and some want SMS messages. Your goal is to use inheritance and polymorphism to handle these different channels without writing separate loops for each.
- Create a base class called
Notificationwith a propertymessage: Stringand a methodsend()that prints "Sending generic notification: [message]". - Create a subclass
EmailNotificationthat adds anemailAddress: Stringproperty and overridessend()to print "Sending Email to [emailAddress]: [message]". - Create a subclass
SMSNotificationthat adds aphoneNumber: Stringproperty and overridessend()to print "Sending SMS to [phoneNumber]: [message]". - Create an array of type
[Notification]containing at least one of each type. - Loop through the array and call
send()on each object to demonstrate polymorphism.
There are no comments for now.