Swift: بروتوكولات Swift
Protocols define a blueprint of methods, properties, and other requirements. They are the cornerstone of Swift's Protocol-Oriented Programming (POP). This lesson covers how to define and conform to protocols, plus the delegate pattern in practice.
1. What You'll Learn
- Using
protocolto define property and method requirements - Conforming classes and structs to protocols
- Protocol inheritance and composition
- Providing default implementations via
extension - The Delegate design pattern
2. A Real-World Payment Developer Story
(1) Pain Point: Each Payment Method Has Its Own Interface
Alice's team needs to integrate multiple payment methods — credit card, PayPal, WeChat Pay:
class CreditCardPayment {
func processCardPayment(amount: Double, cardNumber: String) -> Bool {
// Credit card processing
return true
}
}
class PayPalPayment {
func payWithPayPal(amount: Double, email: String) -> Bool {
// PayPal processing
return true
}
}
class WeChatPayment {
func wechatPay(amount: Double, code: String) -> Bool {
// WeChat Pay processing
return true
}
}
Three classes, three different method names, three different parameter lists. Business logic is littered with if-else checks for the payment type. Adding one more payment method means changing every call site.
(2) Solution: Protocols
protocol PaymentMethod {
func pay(amount: Double) -> Bool
}
struct CreditCard: PaymentMethod {
let cardNumber: String
func pay(amount: Double) -> Bool { /* Process */ true }
}
struct PayPal: PaymentMethod {
let email: String
func pay(amount: Double) -> Bool { /* Process */ true }
}
// Unified handling
func checkout(amount: Double, using method: PaymentMethod) {
method.pay(amount: amount)
}
Don't care what payment method it is — only that it conforms to PaymentMethod.
(3) Benefit: Unified Interface, Extensible
| Dimension | Independent Implementations | Protocol Unification |
|---|---|---|
| Method names | All different | Unified pay(amount:) |
| Adding a method | Change all call sites | Only add a conforming type |
| Testing | Write a test suite for each | Protocol mock for unified testing |
| Coupling | High | Low (Protocol-Oriented Programming) |
3. Defining and Conforming to Protocols
(1) Protocol Syntax
graph TB
A[protocol keyword] --> B[Protocol name]
B --> C[Property requirements]
B --> D[Method requirements]
B --> E[Subscript requirements]
C --> F["var name: String { get set }"]
D --> G["func work()"]
E --> H["subscript(...) -> Type"]
| Protocol Requirement | Syntax | Notes |
|---|---|---|
| Read-write property | { get set } |
var or computed property (get+set) |
| Read-only property | { get } |
let, var, or read-only computed property |
| Instance method | func name() |
Signature only, no implementation |
| mutating method | mutating func name() |
Value types can modify self |
▶ Example: Defining and Conforming to Protocols
// ============================================
// Defining and conforming to a protocol
// ============================================
// 1. Define the protocol
protocol Describable {
var description: String { get }
func summarize() -> String
}
// 2. Struct conforming to the protocol
struct Book: Describable {
let title: String
let author: String
var description: String {
return "\"\(title)\" by \(author)"
}
func summarize() -> String {
return "Book: \(description)"
}
}
// 3. Class conforming to the protocol
class Movie: Describable {
let title: String
let director: String
init(title: String, director: String) {
self.title = title
self.director = director
}
var description: String {
return "\(title) (directed by \(director))"
}
func summarize() -> String {
return "Movie: \(description)"
}
}
let book = Book(title: "1984", author: "George Orwell")
let movie = Movie(title: "Inception", director: "Christopher Nolan")
print(book.summarize())
print(movie.summarize())
Output:
TEXT 📖 للعرض فقطBook: "1984" by George Orwell Movie: Inception (directed by Christopher Nolan)
4. Protocol Inheritance and Composition
(1) Protocol Inheritance
A protocol can inherit from one or more other protocols:
graph TB
A[Protocol: Payable] --> B["Property: amount"]
A --> C["Method: process()"]
B --> D[Protocol: Refundable]
C --> D
D --> E["Method: refund()"]
D --> F[Type: CreditCard]
F --> G["Implements Payable + Refundable"]
▶ Example: Protocol Inheritance
// ============================================
// Protocol inheritance — a refundable payment protocol
// ============================================
protocol Payable {
var amount: Double { get }
func process() -> Bool
}
protocol Refundable: Payable { // Inherits Payable
func refund() -> Bool
}
// CreditCard implements refundable payment
struct CreditCard: Refundable {
let amount: Double
let cardNumber: String
func process() -> Bool {
print("Processing $\(amount) on card \(cardNumber)")
return true
}
func refund() -> Bool {
print("Refunding $\(amount) to card \(cardNumber)")
return true
}
}
// GiftCard can pay but not refund
struct GiftCard: Payable {
let amount: Double
let code: String
func process() -> Bool {
print("Processing $\(amount) with gift card \(code)")
return true
}
}
let payments: [Payable] = [
CreditCard(amount: 100, cardNumber: "1234"),
GiftCard(amount: 50, code: "GIFT-001")
]
for payment in payments {
payment.process()
// Check if also Refundable
if let refundable = payment as? Refundable {
print(" This payment can be refunded")
}
}
Output:
TEXT 📖 للعرض فقطProcessing $100.0 on card 1234 This payment can be refunded Processing $50.0 with gift card GIFT-001
(2) Protocol Composition
Use the & operator to combine multiple protocols, requiring a type to conform to all of them simultaneously:
protocol Identifiable {
var id: String { get }
}
protocol Loggable {
func log()
}
// Type must conform to both Identifiable and Loggable
func saveItem(_ item: Identifiable & Loggable) {
print("Saving item \(item.id)")
item.log()
}
struct User: Identifiable, Loggable {
let id: String
func log() { print("User logged: \(id)") }
}
let user = User(id: "U-001")
saveItem(user)
Output:
TEXT 📖 للعرض فقطSaving item U-001 User logged: U-001
5. Default Implementations via Extension and the Delegate Pattern
(1) Providing Default Implementations with extension
Use extension to provide default implementations for protocol methods — conforming types can choose not to implement them:
// ============================================
// Adding default implementations to protocols via extension
// ============================================
protocol Greetable {
var name: String { get }
func greet() -> String
}
// Default implementation
extension Greetable {
func greet() -> String {
return "Hello, \(name)!"
}
}
// Person uses the default implementation
struct Person: Greetable {
let name: String
// No need to implement greet() — uses the default version
}
// Robot provides a custom implementation
struct Robot: Greetable {
let name: String
func greet() -> String {
return "Beep boop, I am \(name)"
}
}
print(Person(name: "Alice").greet())
print(Robot(name: "R2-D2").greet())
Output:
TEXT 📖 للعرض فقطHello, Alice! Beep boop, I am R2-D2
(2) The Delegate Pattern
The delegate pattern is a design pattern where one object delegates part of its work to another object that conforms to a protocol:
sequenceDiagram
participant A as Class A
participant D as Delegate (protocol)
participant B as Class B
A->>D: An event occurred
D->>B: Calls delegate method
B-->>A: Returns processing result
▶ Example: Implementing the Delegate Pattern
// ============================================
// Delegate pattern: Download manager
// ============================================
// 1. Define the delegate protocol
protocol DownloadDelegate: AnyObject {
func downloadDidStart(_ url: String)
func downloadDidProgress(_ url: String, percent: Double)
func downloadDidComplete(_ url: String, data: String)
func downloadDidFail(_ url: String, error: String)
}
// 2. Download manager — delegates events to an external handler
class DownloadManager {
weak var delegate: DownloadDelegate?
func download(from url: String) {
delegate?.downloadDidStart(url)
// Simulate download progress
for i in 1...5 {
let percent = Double(i) / 5.0 * 100
delegate?.downloadDidProgress(url, percent: percent)
}
// Simulate completion
delegate?.downloadDidComplete(url, data: "Downloaded content from \(url)")
}
}
// 3. View controller — acting as the delegate
class ViewController: DownloadDelegate {
func downloadDidStart(_ url: String) {
print("[UI] Download started: \(url)")
}
func downloadDidProgress(_ url: String, percent: Double) {
print("[UI] \(url): \(Int(percent))%")
}
func downloadDidComplete(_ url: String, data: String) {
print("[UI] Download complete: \(data.prefix(20))...")
}
func downloadDidFail(_ url: String, error: String) {
print("[UI] Download failed: \(error)")
}
}
let manager = DownloadManager()
let ui = ViewController()
manager.delegate = ui // Set the delegate
manager.download(from: "https://example.com/file.zip")
Output:
TEXT 📖 للعرض فقط[UI] Download started: https://example.com/file.zip [UI] https://example.com/file.zip: 20% [UI] https://example.com/file.zip: 40% [UI] https://example.com/file.zip: 60% [UI] https://example.com/file.zip: 80% [UI] https://example.com/file.zip: 100% [UI] Download complete: Downloaded content...Tip: The
AnyObjectconstraint requires the delegate to be a class type (not a struct), allowing the use ofweak varto avoid retain cycles. This is standard practice for the delegate pattern.
6. Complete Example: Configurable Data Validator
// ============================================
// Complete example: Data validation system
// Features: Protocol + protocol inheritance + extension defaults + delegate pattern
// ============================================
import Foundation
// 1. Validation protocol hierarchy
protocol Validatable {
var value: Any { get }
func validate() -> Bool
}
// Validation with detailed error reporting
protocol DetailedValidatable: Validatable {
func errorMessage() -> String
}
// Default implementation
extension Validatable {
func validate() -> Bool { return true }
}
// 2. Concrete validators
struct EmailValidator: DetailedValidatable {
let value: Any
func validate() -> Bool {
guard let email = value as? String else { return false }
return email.contains("@") && email.contains(".")
}
func errorMessage() -> String {
return "Invalid email format"
}
}
struct AgeValidator: DetailedValidatable {
let value: Any
func validate() -> Bool {
guard let age = value as? Int else { return false }
return age >= 18 && age <= 120
}
func errorMessage() -> String {
return "Age must be between 18 and 120"
}
}
struct NonEmptyValidator: Validatable {
let value: Any
// Uses default validate() which returns true, but we customize it
func validate() -> Bool {
guard let text = value as? String else { return false }
return !text.isEmpty
}
}
// 3. Delegate — validation result handler
protocol ValidationDelegate: AnyObject {
func validationDidSucceed(for field: String)
func validationDidFail(for field: String, error: String)
}
// 4. Validation manager
class ValidationManager {
weak var delegate: ValidationDelegate?
private var validators: [(field: String, validator: Validatable)] = []
func addValidator(for field: String, validator: Validatable) {
validators.append((field, validator))
}
func runAll() -> Bool {
var allValid = true
for (field, validator) in validators {
if validator.validate() {
delegate?.validationDidSucceed(for: field)
} else {
allValid = false
let error = (validator as? DetailedValidatable)?.errorMessage() ?? "Validation failed"
delegate?.validationDidFail(for: field, error: error)
}
}
return allValid
}
}
// 5. Usage
class FormController: ValidationDelegate {
func validationDidSucceed(for field: String) {
print("[OK] \(field) is valid")
}
func validationDidFail(for field: String, error: String) {
print("[FAIL] \(field): \(error)")
}
}
let manager = ValidationManager()
manager.delegate = FormController()
manager.addValidator(for: "email", validator: EmailValidator(value: "alice@example.com"))
manager.addValidator(for: "age", validator: AgeValidator(value: 25))
manager.addValidator(for: "name", validator: NonEmptyValidator(value: "Alice"))
let allValid = manager.runAll()
print("Form valid: \(allValid)")
Output:
TEXT 📖 للعرض فقط[OK] email is valid [OK] age is valid [OK] name is valid Form valid: true
❓ FAQ
{ get set }). A base class can provide stored properties and method implementations. A class can conform to multiple protocols but can only inherit from one base class. Protocol-Oriented Programming (POP) is Swift's core design philosophy.DownloadManager holds delegate, and if delegate also holds DownloadManager, a strong reference cycle forms. weak prevents the delegate reference from increasing the reference count. Requiring the delegate to be a class ensures weak can be used, since structs don't support weak references.mutating allows it. Class implementations don't need to write mutating — class methods can modify properties by default.@objc protocols allow the protocol to be used by Objective-C code, commonly seen in UIKit delegate protocols. @objc protocols can have optional requirements (methods that are optional to implement) — a feature pure Swift protocols don't have.📖 Summary
- Protocols define requirements for properties and methods without providing implementations
- Classes, structs, and enums can all conform to protocols
- Protocols support inheritance — one protocol can inherit from multiple parent protocols
- Protocol composition with
&requires a type to conform to multiple protocols simultaneously extensionprovides default implementations for protocols; conforming types can override them- The delegate pattern uses weak protocol references to avoid retain cycles
📝 Exercises
- Basic: Define a
Flyableprotocol with afly()method. Create aBirdstruct and anAirplaneclass that conform to it, each implementingfly(). - Intermediate: Define an
Encryptableprotocol (func encrypt(_: String) -> String) and aDecryptableprotocol (func decrypt(_: String) -> String). Have aCaesarCipherclass conform to both (Caesar cipher, shift of 3). Use protocol compositionEncryptable & Decryptableas the parameter type. - Challenge: Implement a "Sortable Data Source" delegate pattern: define a
SortableDataSourceprotocol (func numberOfItems() -> Intandfunc item(at index: Int) -> String), and aSortDelegateprotocol (func didSort(items: [String])). Create aNameListclass conforming toSortableDataSourceand aTableViewclass conforming toSortDelegate.NameListnotifiesTableViewafter sorting.