-
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
130: Common Swift Interview Questions on Protocol-Oriented Design
If you're heading into a Swift interview, you're almost certainly going to get hit with questions about Protocol-Oriented Programming (POP). Most candidates can define what a protocol is, but very few can actually explain why we use them over traditional class inheritance. To get this right, stop thinking about "what an object is" and start thinking about "what an object can do."
Think of it like a professional kitchen. In a traditional Object-Oriented world, you'd have a base class called Employee, then a subclass Chef, and maybe a PastryChef under that. But what happens when you hire a Dishwasher who also knows how to bake? In a class hierarchy, you're stuck. You can't inherit from both Dishwasher and PastryChef.
In a Protocol-Oriented world, we don't care about the job title. We care about capabilities. We define a Baking protocol and a Cleaning protocol. Now, anyone—be they a professional chef, a dishwasher, or the owner—can conform to Baking. You're composing a person's skill set rather than forcing them into a rigid family tree.
The "Default Implementation" Trap
One of the most common interview questions is: "How does a protocol differ from an abstract class?" Since Swift doesn't have abstract classes, we use protocol extensions. This is where the real power lies.
I've seen a lot of developers just put method signatures in a protocol and leave the implementation to the struct. That's just an interface. To make it "Protocol-Oriented," you provide a default behavior in an extension. Look at this payment example:
protocol PaymentProcessor {
var merchantId: String { get }
func processPayment(amount: Double)
}
extension PaymentProcessor {
// This is the "default" behavior.
// Most processors will use this, but any specific one can override it.
func processPayment(amount: Double) {
print("Processing $\(amount) for merchant \(merchantId) via standard gateway.")
}
}
struct StripeProcessor: PaymentProcessor {
let merchantId = "STRIPE_123"
// I don't need to implement processPayment here if the default works.
}
struct CryptoProcessor: PaymentProcessor {
let merchantId = "BLOCK_99"
// But for Crypto, the standard gateway doesn't work, so I override it.
func processPayment(amount: Double) {
print("Processing $\(amount) via Blockchain wallet \(merchantId).")
}
}
In an interview, emphasize that this allows you to add functionality to any type that conforms to the protocol without forcing every single struct to rewrite the same boilerplate code.
Composition Over Inheritance
You'll likely be asked why you'd prefer protocols and structs over classes. The answer is usually "value semantics" and "composition." When you use classes, you're dealing with shared state and reference counting, which can lead to those nightmare retain cycles we've discussed in previous lessons.
By using protocols, you can build complex objects by mixing and matching small, focused protocols. Instead of a massive BaseViewModel that does everything, you create a LoadingStateProviding protocol and a NetworkRequesting protocol. Your ViewModel then just adopts the ones it actually needs. It keeps your code decoupled and, more importantly, makes your unit tests much easier to write because you can mock a single protocol instead of a whole class hierarchy.
Decoding 'some' vs 'any'
This is the "senior-level" question that trips people up. With recent Swift updates, the distinction between some (Opaque Types) and any (Existential Types) is a hot topic.
some PaymentProcessor: You're telling the compiler, "I'm returning one specific type that conforms to this protocol, but I'm not going to tell you exactly which one it is." This preserves type identity and is generally more performant.any PaymentProcessor: You're saying, "This could be a StripeProcessor, a CryptoProcessor, or anything else that fits the bill." The compiler has to use a "box" to hold this value, which adds a tiny bit of overhead.
If the interviewer asks which one to use, the rule of thumb is: Use some whenever possible. Only reach for any when you truly need a collection of different types (like an array [any PaymentProcessor]) that all behave differently.
📋 Practical Task
Build a Modular Notification Dispatcher
Your task is to design a notification system using Protocol-Oriented Design. Avoid using classes entirely; use structs and protocols.
- Create a protocol named
NotificationServicethat requires aserviceName: Stringand a methodsend(message: String). - Create a protocol extension for
NotificationServicethat provides a default implementation forsend(message:), printing:"[\(serviceName)] Sending generic notification: \(message)". - Implement three structs:
EmailService,SmsService, andPushNotificationService.EmailServiceshould use the defaultsendbehavior.SmsServiceshould overridesendto print:"SMS: Sending short message: \(message)".PushNotificationServiceshould use the defaultsendbehavior.
- Create a
NotificationDispatcherstruct that contains an array ofany NotificationService. It should have a methoddispatchAll(message: String)that iterates through all services and callssend(message:)on each. - Instantiate the dispatcher with all three services and trigger a single message to be sent across all channels.
There are no comments for now.