Protocols and Extensions
Protocols and Extensions
Protocols and extensions are essential features of Swift that enhance code modularity, flexibility, and reusability. Protocols define a blueprint of methods, properties, or other requirements, while extensions allow you to add functionality to existing types.
35. Protocol Definition and Conformance
What is a Protocol?
A protocol in Swift is a blueprint of methods, properties, and other requirements that a type must implement. Protocols enable polymorphism and are central to protocol-oriented programming.
Defining a Protocol
protocol Vehicle {
var speed: Double { get set }
func move()
}
• Properties: Protocols specify properties, but the actual implementation is left to conforming types.
• Methods: Protocols declare methods without implementation.
Conforming to a Protocol
Types (classes, structs, or enums) that conform to a protocol must implement all its requirements.
Example: Class Conformance
class Car: Vehicle {
var speed: Double = 0.0
func move() {
print("Car is moving at \(speed) km/h")
}
}
let myCar = Car()
myCar.speed = 100
myCar.move() // Output: Car is moving at 100.0 km/h
Example: Struct Conformance
struct Bicycle: Vehicle {
var speed: Double
func move() {
print("Bicycle is moving at \(speed) km/h")
}
}
let myBike = Bicycle(speed: 15)
myBike.move() // Output: Bicycle is moving at 15.0 km/h
Protocol Inheritance
A protocol can inherit from other protocols.
protocol Drivable: Vehicle {
var isElectric: Bool { get }
}
class ElectricCar: Drivable {
var speed: Double = 0.0
var isElectric: Bool = true
func move() {
print("Electric car is moving silently at \(speed) km/h")
}
}
Optional Requirements (for @objc Protocols)
Optional protocol methods can be declared using the @objc keyword.
@objc protocol DataSource {
@objc optional func fetchData()
}
class MyDataSource: DataSource {
func fetchData() {
print("Data fetched!")
}
}
36. Extensions
What is an Extension?
Extensions in Swift allow you to add new functionality to existing types (classes, structs, enums, or protocols) without modifying the original source code.
Adding Methods with Extensions
Example: Adding a Method to Int
extension Int {
func squared() -> Int {
return self * self
}
}
print(4.squared()) // Output: 16
Adding Computed Properties
Example: Adding a Property to String
extension String {
var isPalindrome: Bool {
return self == String(self.reversed())
}
}
print("level".isPalindrome) // Output: true
Adding Protocol Conformance with Extensions
Extensions can make a type conform to a protocol.
protocol Printable {
func printInfo()
}
extension Int: Printable {
func printInfo() {
print("This is an integer: \(self)")
}
}
5.printInfo() // Output: This is an integer: 5
Nested Extensions
Extensions can include nested types to group related functionality.
extension Array {
struct Summary {
let count: Int
let isEmpty: Bool
}
func summary() -> Summary {
return Summary(count: self.count, isEmpty: self.isEmpty)
}
}
let array = [1, 2, 3]
let summary = array.summary()
print(summary.count) // Output: 3
37. Protocol-Oriented Programming
What is Protocol-Oriented Programming?
Protocol-oriented programming (POP) is a design paradigm in Swift where protocols define the core behavior of your application. Instead of relying heavily on inheritance (OOP), you compose behaviors through protocol conformance.
Advantages of POP
1. Flexibility: Types can conform to multiple protocols, avoiding rigid inheritance hierarchies.
2. Modularity: Behaviors are decoupled and reusable.
3. Code Consistency: Protocols enforce consistent behavior across different types.
Protocol Extensions
Protocol extensions provide default implementations for protocol methods, enabling shared behavior across conforming types.
Example: Default Implementation
protocol Drawable {
func draw()
}
extension Drawable {
func draw() {
print("Default drawing")
}
}
struct Circle: Drawable {}
struct Square: Drawable {}
Circle().draw() // Output: Default drawing
Square().draw() // Output: Default drawing
Combining Protocols
Example: Composing Multiple Protocols
protocol Flyable {
func fly()
}
protocol Swimmable {
func swim()
}
struct Bird: Flyable, Swimmable {
func fly() {
print("Bird is flying")
}
func swim() {
print("Bird is swimming")
}
}
let penguin = Bird()
penguin.fly() // Output: Bird is flying
penguin.swim() // Output: Bird is swimming
Generic Constraints with Protocols
Example: Using where with Protocols
func printDrawable<T: Drawable>(_ item: T) {
item.draw()
}
38. Delegation Pattern
What is the Delegation Pattern?
The delegation pattern is a design pattern where one object (the delegator) delegates responsibility for a task to another object (the delegate). It is commonly implemented using protocols in Swift.
Components of the Delegation Pattern
1. Delegator: The object that delegates a task.
2. Delegate: The object that performs the task.
3. Protocol: Defines the task to be performed.
Example of Delegation
Step 1: Define a Protocol
protocol TaskDelegate: AnyObject {
func taskDidComplete(message: String)
}
Step 2: Create a Delegator Class
class Task {
weak var delegate: TaskDelegate?
func performTask() {
print("Task is being performed...")
delegate?.taskDidComplete(message: "Task completed successfully!")
}
}
Step 3: Conform to the Protocol
class TaskHandler: TaskDelegate {
func taskDidComplete(message: String) {
print(message)
}
}
Step 4: Set the Delegate
let task = Task()
let handler = TaskHandler()
task.delegate = handler
task.performTask()
// Output:
// Task is being performed...
// Task completed successfully!
Real-World Example: UITableView Delegation
In UIKit, UITableView uses the delegation pattern for tasks like rendering cells.
1. Protocol: UITableViewDelegate
2. Delegator: The UITableView object.
3. Delegate: The view controller implementing UITableViewDelegate.
Advanced Features of Delegation
1. Using @objc for Optional Methods:
@objc protocol OptionalDelegate {
@objc optional func optionalMethod()
}
2. Avoid Retain Cycles with weak:
Always declare delegates as weak to prevent strong reference cycles.
3. Custom Delegates with Associated Types:
protocol GenericDelegate {
associatedtype Data
func handle(data: Data)
}
Comments
Post a Comment