Working with Protocols
1. Defining Protocols
| Property | Description | Example |
|---|---|---|
| protocol Name | Defines a new protocol with a name | |
| protocol Inheritable | Protocols can inherit from other protocols | |
| protocol Requirements | Protocols can define requirements for properties, methods, and initializers | |
Example: Basic Protocol Definition
protocol Animal {
var name: String { get set }
func makeSound()
}
Example: Protocol with Method Requirements
protocol Drawable {
func draw()
func resize(to size: CGSize)
}
2. Using Property Requirements
| Requirement | Type | Description |
|---|---|---|
| name | String |
Property requirement for the name of the object |
| age | Int |
Property requirement for the age of the object |
Example: Defining a Protocol with Property Requirements
protocol Person {
var name: String { get set }
var age: Int { get set }
}
Example: Conforming to a Protocol with Property Requirements
struct Employee: Person {
var name: String
var age: Int
}
3. Using Method Requirements
| Requirement | Description | Example |
|---|---|---|
| method signature | Defines the method's name, parameters, and return type | func calculateTotal(items: [Int]) -> Int |
| required methods | Methods that must be implemented by conforming types | func process(data: String) -> Bool |
| optional methods | Methods that can be optionally implemented using @objc |
@objc optional func handleEvent() |
Example: Protocol with Method Requirements
protocol Drawable {
func draw()
func area() -> Double
}
struct Circle: Drawable {
let radius: Double
func draw() {
print("Drawing a circle with radius \(radius)")
}
func area() -> Double {
return Double.pi * radius * radius
}
}
Note: Method requirements can include parameters and return types, defining the interface that conforming types must implement.
4. Using Initializer Requirements
| Requirement | Description | Example |
|---|---|---|
| required | Marks an initializer that must be implemented by conforming types | |
| convenience | Allows defining a secondary initializer that calls another initializer in the same type | |
Example: Protocol with Required Initializer
protocol Vehicle {
init(wheels: Int)
var wheels: Int { get }
}
class Car: Vehicle {
let wheels: Int
required init(wheels: Int) {
self.wheels = wheels
}
}
Note: When a protocol requires an initializer, any type conforming to that protocol must implement it.
5. Conforming to Protocols
| Protocol | Conformance Requirements | Notes |
|---|---|---|
| Equatable | Implement == and != operators |
Used for value equality checks |
| Hashable | Implement hash(into:) and conform to Equatable |
Required for use in Sets or as Dictionary keys |
| Comparable | Implement <, <=, >, >= operators |
Allows sorting and comparison operations |
Example: Conforming to Equatable
struct Person: Equatable {
let name: String
let age: Int
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name && lhs.age == rhs.age
}
}
Example: Conforming to Hashable
struct User: Hashable {
let id: String
let name: String
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(name)
}
}
Note: When conforming to
Hashable, ensure that the hash value is consistent for the same object during its lifetime.6. Using Protocol Inheritance
| Protocol | Description | Example |
|---|---|---|
| Protocol Inheritance | Defines a protocol that inherits from one or more other protocols | |
Example: Protocol Inheritance
protocol Drawable {
func draw()
}
protocol Shape: Drawable {
var area: Double { get }
}
struct Circle: Shape {
var radius: Double
func draw() {
print("Drawing a circle")
}
var area: Double {
return Double.pi * radius * radius
}
}
Note: A protocol can inherit from multiple protocols using a comma-separated list.
7. Using Class-Only Protocols
Class-only protocols in Swift are protocols that can only be adopted by class types. They are declared using the class keyword in the protocol definition.
| Feature | Description | Notes |
|---|---|---|
| Protocol Declaration | protocol SomeProtocol: class { } |
Ensures that only classes can conform to this protocol |
| Inheritance | class SomeProtocol: AnotherProtocol { } |
Class-only protocols can inherit from other class-only or regular protocols |
| Property Requirements | var someProperty: String { get set } |
Properties must be declared as var, not let |
| Method Requirements | func someMethod() |
Methods can be required or optional |
Example: Class-Only Protocol Usage
protocol Animal: class {
var name: String { get set }
func makeSound() -> String
}
class Dog: Animal {
var name: String
init(name: String) {
self.name = name
}
func makeSound() -> String {
return "Woof!"
}
}
let dog = Dog(name: "Buddy")
print(dog.makeSound()) // Output: Wof!
Note: Class-only protocols are useful when you want to ensure that a protocol is only adopted by class types, which allows for features like weak references and delegation patterns.
8. Using Protocol Composition (&)
Protocol composition allows you to combine multiple protocols into a single type constraint using the & operator. This is useful when a type must conform to several protocols simultaneously.
| Protocol | Description | Usage |
|---|---|---|
| ProtocolA & ProtocolB | Combines two or more protocols into one type constraint | func process(item: SomeType & ProtocolA & ProtocolB) |
Example: Protocol Composition in Function Parameter
protocol Drawable {
func draw()
}
protocol Movable {
func move(to: CGPoint)
}
struct Circle: Drawable, Movable {
func draw() { print("Drawing circle") }
func move(to point: CGPoint) { print("Moving to \(point)") }
}
func render(shape: Circle & Drawable & Movable) {
shape.draw()
shape.move(to: .zero)
}
let circle = Circle()
render(shape: circle)
Note: Protocol composition is particularly useful in generic contexts where a type must conform to multiple protocols to satisfy constraints.
9. Using Optional Requirements
Protocols in Swift can define optional requirements using the @objc attribute. These requirements are not required to be implemented by conforming types, and can be conditionally checked at runtime.
| Requirement | Description | Implementation |
|---|---|---|
| @objc | Marks a protocol as requiring Objective-C compatibility | Must be applied to the protocol declaration |
| optional | Defines a method or property as optional in the protocol | Must be used with @objc and only in Objective-C compatible protocols |
Example: Defining an Optional Protocol Requirement
protocol OptionalProtocol {
@objc optional func optionalMethod()
@objc optional var optionalProperty: String { get set }
}
class MyClass: OptionalProtocol {
// optionalMethod and optionalProperty are not required to be implemented
}
Note: Optional protocol requirements are only available in Objective-C compatible protocols. They are not supported in native Swift protocols.
Warning: When using optional protocol requirements, always check for their implementation before calling them to avoid runtime errors.
10. Understanding Protocol Extensions
| Feature | Description | Notes |
|---|---|---|
| Protocol Extension | Allows adding methods, properties, and initializers to a protocol | Can provide default implementations for protocol requirements |
| Default Implementation | Methods and properties can have default implementations in extensions | Conforming types can override these defaults |
| Conditional Conformance | Extensions can conditionally conform types to protocols | Uses where clauses to restrict conformance |
Example: Protocol Extension with Default Implementation
protocol Drawable {
func draw()
}
extension Drawable {
func draw() {
print("Drawing default shape")
}
}
struct Circle: Drawable {}
let circle = Circle()
circle.draw() // Output: "Drawing default shape"
Example: Conditional Conformance
protocol Container {
associatedtype Item
func append(_ item: Item)
var count: Int { get }
}
extension Container where Item: Equatable {
func contains(_ item: Item) -> Bool {
for index in 0..<count {
if self[index] == item {
return true
}
}
return false
}
}
Note: Protocol extensions can add default implementations to protocol requirements, but they cannot override or remove requirements from the original protocol.
11. Using Protocol-Oriented Programming
Protocol-oriented programming (POP) is a core paradigm in Swift that emphasizes defining behavior through protocols and leveraging protocol extensions to provide default implementations.
| Concept | Description | Usage |
|---|---|---|
| Protocol | A blueprint of methods, properties, and other requirements that conforming types must implement. | protocol Drawable { func draw() } |
| Protocol Extension | Allows adding default implementations to protocols, enabling shared behavior across conforming types. | extension Drawable { func draw() { print("Drawing...") } } |
| Associated Type | A placeholder for a type that will be specified by the conforming type. | protocol Container { associatedtype Item; func addItem(_ item: Item) } |
| Protocol Inheritance | Protocols can inherit from other protocols, extending their requirements. | protocol Drawable: Renderable { func draw() } |
Example: Protocol-Oriented Design
protocol Flyable {
var maxAltitude: Double { get }
func fly()
}
extension Flyable {
func fly() {
print("Flying up to \(maxAltitude) meters")
}
}
struct Bird: Flyable {
var maxAltitude: Double = 1000.0
}
let eagle = Bird()
eagle.fly() // Prints: Flying up to 1000.0 meters
Note: POP encourages composition over inheritance, leading to more flexible and reusable code.