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á
- Usando
protocolpara definir requisitos de propriedades e métodos - Conformidade de classes e estruturas com protocolos
- Herança e composição do protocolo
- Fornecendo implementações padrão via
extension - O padrão de design Delegado
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:
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
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
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
// ============================================
// 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:
TEXTOLivro: "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:
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
// ============================================
// 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:
TEXTOProcessando 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:
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:
TEXTOSalvando 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:
// ============================================
// 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:
TEXTOOlá, 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:
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
// ============================================
// 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
AnyObjectexige que o delegado seja um tipo de classe (não uma estrutura), permitindo o uso deweak varpara evitar ciclos de retenção. Esta é uma prática padrão para o padrão delegado.
6. Exemplo completo: validador de dados configurável
// ============================================
// 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.DownloadManagercontémdelegate, e sedelegatetambém contémDownloadManager, um forte ciclo de referência se forma.weakevita que a referência do delegado aumente a contagem de referências. Exigir que o delegado seja uma classe garante queweakpossa 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 - adicionarmutatingpermite isso. Implementações de classe não precisam escrevermutating— os métodos de classe podem modificar propriedades por padrão. P: Para que são usados os protocolos @objc? R: Os protocolos@objcpermitem que o protocolo seja usado pelo código Objective-C, comumente visto em protocolos delegados do UIKit. Os protocolos @objc podem ter requisitosoptional(métodos cuja implementação é opcional) - um recurso que os protocolos Swift puros não possuem.
📖 Resumo
- Os protocolos definem requisitos para propriedades e métodos sem fornecer implementações
- Classes, estruturas e enums podem estar em conformidade com protocolos
- Protocolos suportam herança — um protocolo pode herdar de vários protocolos pais
- A composição do protocolo com
&requer um tipo para estar em conformidade com vários protocolos simultaneamente extensionfornece implementações padrão para protocolos; tipos em conformidade podem substituí-los- O padrão delegado usa referências de protocolo fracas para evitar ciclos de retenção
📝 Exercícios
- Básico: Defina um protocolo
Flyablecom um métodofly(). Crie uma estruturaBirde uma classeAirplaneque esteja em conformidade com ela, cada uma implementandofly(). - Intermediário: Defina um protocolo
Encryptable(func encrypt(_: String) -> String) e um protocoloDecryptable(func decrypt(_: String) -> String). Tenha uma classeCaesarCipherem conformidade com ambos (cifra de César, mudança de 3). Use a composição do protocoloEncryptable & Decryptablecomo tipo de parâmetro. - Desafio: Implementar um padrão de delegado "Fonte de dados classificável": definir um protocolo
SortableDataSource(func numberOfItems() -> Intefunc item(at index: Int) -> String) e um protocoloSortDelegate(func didSort(items: [String])). Crie uma classeNameListem conformidade comSortableDataSourcee uma classeTableViewem conformidade comSortDelegate.NameListnotificaTableViewapós a classificação.