Swift: Protocolos Swift

Os protocolos definem um modelo de métodos, propriedades e outros requisitos. Eles são a base da Programação Orientada a Protocolo (POP) do Swift. Esta lição aborda como definir e estar em conformidade com protocolos, além do padrão de delegação na prática.

1. O que você aprenderá


2. Uma história de desenvolvedor de pagamentos do mundo real

(1) Ponto problemático: cada método de pagamento tem sua própria interface

A equipe de Alice precisa integrar vários métodos de pagamento — cartão de crédito, PayPal, WeChat Pay:

SWIFT
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
    }
}

Três classes, três nomes de métodos diferentes, três listas de parâmetros diferentes. A lógica de negócios está repleta de verificações if-else para o tipo de pagamento. Adicionar mais uma forma de pagamento significa mudar cada site de chamada.

(2) Solução: Protocolos

SWIFT
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)
}

Não importa qual método de pagamento seja - apenas se está em conformidade com PaymentMethod.

(3) Benefício: Interface unificada, extensível

Dimensão Implementações Independentes Unificação de Protocolo
Nomes de métodos Todos diferentes Unificado pay(amount:)
Adicionando um método Alterar todos os locais de chamada Adicione apenas um tipo em conformidade
Teste Escreva um conjunto de testes para cada Simulação de protocolo para testes unificados
Acoplamento Alto Baixo (Programação Orientada a Protocolo)

3. Definição e conformidade com protocolos

(1) Sintaxe do protocolo

100%
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"]
Requisito de protocolo Sintaxe Notas
Propriedade de leitura e gravação { get set } var ou propriedade computada (get+set)
Propriedade somente leitura { get } let, var ou propriedade computada somente leitura
Método de instância func name() Apenas assinatura, sem implementação
método de mutação mutating func name() Os tipos de valor podem modificar self

▶ Exemplo: Definição e conformidade com protocolos

SWIFT
// ============================================
// 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())

Saída:

TEXTO
Livro: "1984" de George Orwell
Filme: A Origem (dirigido por Christopher Nolan)

4. Herança e composição do protocolo

(1) Herança de protocolo

Um protocolo pode herdar de um ou mais protocolos:

100%
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"]

▶ Exemplo: herança de protocolo

SWIFT
// ============================================
// 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")
    }
}

Saída:

TEXTO
Processando US$ 100,0 no cartão 1234
Este pagamento pode ser reembolsado
Processando US$ 50,0 com cartão-presente GIFT-001

(2) Composição do Protocolo

Use o operador & para combinar vários protocolos, exigindo um tipo que esteja em conformidade com todos eles simultaneamente:

SWIFT
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)

Saída:

TEXTO
Salvando item U-001
Usuário logado: U-001

5. Implementações padrão via extensão e padrão delegado

(1) Fornecendo implementações padrão com extensão

Use extension para fornecer implementações padrão para métodos de protocolo — tipos em conformidade podem optar por não implementá-los:

SWIFT
// ============================================
// 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())

Saída:

TEXTO
Olá, Alice!
Beep boop, eu sou R2-D2

(2) O padrão de delegado

O padrão delegado é um padrão de design onde um objeto delega parte de seu trabalho para outro objeto que esteja em conformidade com um protocolo:

100%
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

▶ Exemplo: Implementando o Padrão Delegado

SWIFT
// ============================================
// 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")

Saída:

TEXTO
[IU] Download iniciado: https://example.com/file.zip
[IU] https://example.com/file.zip: 20%
[IU] https://example.com/file.zip: 40%
[IU] https://example.com/file.zip: 60%
[IU] https://example.com/file.zip: 80%
[IU] https://example.com/file.zip: 100%
[UI] Download concluído: conteúdo baixado...

Dica: A restrição AnyObject exige que o delegado seja um tipo de classe (não uma estrutura), permitindo o uso de weak var para evitar ciclos de retenção. Esta é uma prática padrão para o padrão delegado.


6. Exemplo completo: validador de dados configurável

SWIFT
// ============================================
// 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)")

Saída:

TEXTO
[OK] e-mail é válido
[OK] a idade é válida
[OK] nome é válido
Formulário válido: verdadeiro

❓ Perguntas Frequentes

P: Qual é a diferença entre um protocolo e uma classe base? R: Um protocolo define apenas um modelo de interface — ele não pode armazenar propriedades (apenas declarar { get set }). Uma classe base pode fornecer propriedades armazenadas e implementações de métodos. Uma classe pode estar em conformidade com vários protocolos, mas só pode herdar de uma classe base. A Programação Orientada a Protocolo (POP) é ​​a principal filosofia de design do Swift. P: Quando usar implementações padrão de protocolo versus uma classe base? R: Use uma classe base quando precisar de propriedades armazenadas compartilhadas ou inicializadores herdados. Use extensões de protocolo quando precisar apenas definir uma interface comportamental com implementações padrão. Prefira protocolos porque são mais flexíveis (conformidade com vários protocolos). P: Por que usar var fraco para delegados? R: Para evitar ciclos de retenção. DownloadManager contém delegate, e se delegate também contém DownloadManager, um forte ciclo de referência se forma. weak evita que a referência do delegado aumente a contagem de referências. Exigir que o delegado seja uma classe garante que weak possa ser usado, uma vez que as estruturas não suportam referências fracas. P: Qual é o propósito da mutação de métodos em protocolos? R: Marca um método que pode modificar as próprias propriedades do tipo em conformidade. Os métodos Struct e enum não podem modificar propriedades por padrão - adicionar mutating permite isso. Implementações de classe não precisam escrever mutating — os métodos de classe podem modificar propriedades por padrão. P: Para que são usados ​​os protocolos @objc? R: Os protocolos @objc permitem que o protocolo seja usado pelo código Objective-C, comumente visto em protocolos delegados do UIKit. Os protocolos @objc podem ter requisitos optional (métodos cuja implementação é opcional) - um recurso que os protocolos Swift puros não possuem.


📖 Resumo


📝 Exercícios

  1. Básico: Defina um protocolo Flyable com um método fly(). Crie uma estrutura Bird e uma classe Airplane que esteja em conformidade com ela, cada uma implementando fly().
  2. Intermediário: Defina um protocolo Encryptable (func encrypt(_: String) -> String) e um protocolo Decryptable (func decrypt(_: String) -> String). Tenha uma classe CaesarCipher em conformidade com ambos (cifra de César, mudança de 3). Use a composição do protocolo Encryptable & Decryptable como tipo de parâmetro.
  3. Desafio: Implementar um padrão de delegado "Fonte de dados classificável": definir um protocolo SortableDataSource (func numberOfItems() -> Int e func item(at index: Int) -> String) e um protocolo SortDelegate (func didSort(items: [String])). Crie uma classe NameList em conformidade com SortableDataSource e uma classe TableView em conformidade com SortDelegate. NameList notifica TableView após a classificação.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%