Swift: Extensões e Controle de Acesso do Swift
As extensões permitem adicionar novas funcionalidades a classes, structs, enums ou protocolos existentes. O controle de acesso torna as interfaces do seu código mais claras e o encapsulamento mais rígido. Esta lição combina ambos para ajudar você a escrever código Swift mais elegante.
1. O Que Você Vai Aprender
- Adicionar métodos e propriedades a tipos existentes usando
extension - Estar em conformidade com protocolos via extensões
- Extensões de propriedades computadas
- Os cinco níveis de controle de acesso do Swift
- Práticas recomendadas de controle de acesso
2. Uma História Real de um Desenvolvedor Back-End
(1) Problema: Não É Possível Adicionar Funcionalidade ao String do Foundation
Ao trabalhar na formatação de datas, Bob percebeu que Date não tinha um método para converter para um formato legível de "há quanto tempo". Ele escreveu uma função utilitária:
func timeAgo(from date: Date) -> String {
let seconds = Date().timeIntervalSince(date)
if seconds < 60 { return "\(Int(seconds))s ago" }
if seconds < 3600 { return "\(Int(seconds / 60))m ago" }
return "\(Int(seconds / 3600))h ago"
}
// Must pass the date as argument every time
print(timeAgo(from: someDate))
Cada chamada exige passar o argumento e invocar uma função global -- o código não parece natural. Bob queria que Date retornasse esse formato por conta própria -- como um método embutido.
(2) A Solução com extension
extension Date {
func timeAgo() -> String {
let seconds = Date().timeIntervalSince(self)
if seconds < 60 { return "\(Int(seconds))s ago" }
if seconds < 3600 { return "\(Int(seconds / 60))m ago" }
return "\(Int(seconds / 3600))h ago"
}
}
// Looks like a built-in Date method
print(someDate.timeAgo())
Extensões dão a Date um novo método -- sem herança, sem modificar o código fonte.
(3) Benefícios: Sintaxe Natural + Alta Coesão
| Dimensão | Função Global | Extension |
|---|---|---|
| Estilo de chamada | tempoAtras(de: data) |
data.tempoAtras() |
| Organização do código | Espalhado por toda parte | Agrupado com o tipo |
| Descoberta | Deve importar manualmente o módulo utilitário | Autocompletar do Xcode |
| Manutenibilidade | Poluição do namespace global | Namespace específico do tipo |
3. Extensões
(1) Sintaxe de Extension
As extensões podem adicionar o seguinte a tipos existentes:
| Pode Adicionar | Exemplo | Limitação |
|---|---|---|
| Propriedades computadas | var area: Double { ... } |
Não pode adicionar propriedades armazenadas |
| Métodos de instância/tipo | func invertido() -> String |
Permitido |
| Inicializadores | init?(json: [String: Any]) |
Não pode adicionar deinit |
| Subscritos | subscript(idx: Int) -> T |
Permitido |
| Tipos aninhados | enum Status { ... } |
Permitido |
| Conformidade com protocolo | extension Tipo: Protocolo |
Permitido |
graph TB
A["extension AlgumTipo {"] --> B["Propriedades Computadas"]
A --> C["Métodos"]
A --> D["Inicializadores"]
A --> E["Subscritos"]
A --> F["Tipos Aninhados"]
A --> G["Conformidade com Protocolo"]
A --> H["}"]
▶ Exemplo: Adicionando Extensões a String
// ============================================
// Adding practical extensions to String
// ============================================
extension String {
// Computed property -- check if this is a valid email
var isValidEmail: Bool {
return contains("@") && contains(".")
}
// Method -- truncate to first N characters
func prefix(_ maxLength: Int) -> String {
guard count > maxLength else { return self }
return String(self.prefix(maxLength)) + "..."
}
// Method -- reverse the string
func reversed() -> String {
return String(self.reversed())
}
}
let email = "alice@example.com"
print("\(email) is valid: \(email.isValidEmail)")
let longText = "This is a very long string that needs truncation"
print(longText.prefix(20))
print("Hello".reversed())
Saída:
TEXT 📖 Somente leituraalice@example.com is valid: true This is a very lon... olleHNota: Extensões não podem sobrescrever métodos existentes. Se uma extensão definir um método com o mesmo nome de um existente, o compilador reportará um erro.
(2) Conformidade com Protocolos via Extensões
Separe a conformidade com protocolo da definição do tipo implementando-a em uma extensão:
// ============================================
// Conforming to protocols via extensions
// ============================================
// 1. Define the base type
struct User {
let id: Int
let name: String
let email: String
}
// 2. Conform to Hashable via extension (for Set and Dictionary keys)
extension User: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
// 3. Conform to Equatable via extension
extension User: Equatable {
static func == (lhs: User, rhs: User) -> Bool {
return lhs.id == rhs.id
}
}
// 4. Usage
let users: Set<User> = [
User(id: 1, name: "Alice", email: "alice@example.com"),
User(id: 1, name: "Alice", email: "alice@example.com"), // Duplicate -- deduplicated
User(id: 2, name: "Bob", email: "bob@example.com")
]
print("Users count: \(users.count)") // 2
Saída:
TEXT 📖 Somente leituraUsers count: 2Dica: Colocar a conformidade com protocolo em extensões é para organização de código -- a definição do tipo contém apenas os dados principais, e os comportamentos são adicionados via extensões. Este é um padrão comum na biblioteca padrão do Swift.
4. Controle de Acesso
(1) Cinco Níveis de Acesso
O Swift fornece cinco níveis de controle de acesso, do mais aberto ao mais fechado:
graph TB
A[Pirâmide de Controle de Acesso] --> B["open - Mais aberto, qualquer um pode acessar/herdar/sobrescrever"]
A --> C["public - Qualquer um pode acessar, mas não pode herdar/sobrescrever"]
A --> D["internal - Dentro do módulo atual (nível padrão)"]
A --> E["fileprivate - Dentro do arquivo atual"]
A --> F["private - Dentro do escopo da declaração envolvente"]
| Nível | Palavra-chave | Escopo de Acesso | Usável Em |
|---|---|---|---|
| Mais aberto | open |
Qualquer módulo, pode herdar e sobrescrever | Classes, métodos de classe |
public |
Qualquer módulo, mas não pode herdar/sobrescrever | Classes, métodos, propriedades | |
| Padrão | internal |
Módulo atual (App ou Framework) | Todos os tipos |
fileprivate |
Arquivo atual | Tipos, métodos, propriedades | |
| Mais fechado | private |
Escopo de declaração atual (chaves) | Propriedades, métodos |
▶ Exemplo: Controle de Acesso em Ação
// ============================================
// Using the five access levels
// ============================================
// Public class available to all modules
public class BankAccount {
// Publicly readable, privately writable
public private(set) var balance: Double
// Private field -- only accessible within this class
private var accountNumber: String
private var transactionHistory: [String] = []
// Accessible within this file -- for logging
fileprivate var lastTransactionDate: Date?
// Accessible within this module -- for internal auditing
internal var branchCode: String
public init(accountNumber: String, balance: Double, branchCode: String) {
self.accountNumber = accountNumber
self.balance = balance
self.branchCode = branchCode
}
public func deposit(amount: Double) {
balance += amount
transactionHistory.append("Deposit: \(amount)")
lastTransactionDate = Date()
}
// Private helper method
private func log(_ message: String) {
print("[Private] \(message)")
}
}
let account = BankAccount(accountNumber: "123-456", balance: 1000, branchCode: "BR-001")
print("Balance: \(account.balance)") // public getter is readable
// account.balance = 2000 // Compile error! public private(set) prevents external writes
// print(account.accountNumber) // Compile error! private property
// print(account.lastTransactionDate) // Compile error! fileprivate, inaccessible from another file
print("Branch: \(account.branchCode)") // internal, accessible within the same module
Saída:
TEXT 📖 Somente leituraBalance: 1000.0 Branch: BR-001
(2) Princípios de Controle de Acesso
| Princípio | Explicação |
|---|---|
| Privilégio mínimo | Padrão private, afrouxe apenas quando necessário |
| Interface estável | APIs públicas são contratos uma vez publicadas — modifique com cuidado |
| Limites do módulo | public para um framework define o limite do módulo; internal é a implementação interna |
| getter/setter | Padrão public private(set): publicamente legível, privadamente gravável |
5. Extensões e Controle de Acesso
(1) Compartilhamento private no Mesmo Arquivo
Membros private são acessíveis dentro de extensões no mesmo arquivo:
// ============================================
// Visibility of private in extensions
// ============================================
struct UserAccount {
private var token: String = "secret-token"
let name: String
func getToken() -> String {
return token
}
}
// Extensions in the same file can access private members
extension UserAccount {
func resetToken() {
token = "new-token" // Accessible! Same file
}
}
var account = UserAccount(name: "Alice")
print(account.getToken())
account.resetToken()
print(account.getToken())
Saída:
TEXT 📖 Somente leiturasecret-token new-token
▶ Exemplo: Padrão de Organização com Extensões
// Core data definition
struct Product {
let id: Int
let name: String
let price: Double
}
// MARK: - Equatable Conformance
extension Product: Equatable {
static func == (lhs: Product, rhs: Product) -> Bool {
return lhs.id == rhs.id
}
}
// MARK: - Codable Conformance
extension Product: Codable { }
// MARK: - Utility Methods
extension Product {
func formattedPrice() -> String {
return "$\(String(format: "%.2f", price))"
}
var priceWithTax: Double {
return price * 1.08
}
}
▶ Example: Adding Nested Types and Convenience Initializers via Extensions
Extensions can add not only methods and computed properties, but also nested types and initializers:
// ============================================
// Adding nested types and convenience initializers via extensions
// ============================================
// Base type
struct Book {
let title: String
let author: String
let year: Int
}
// Extension: add nested types and convenience initializers
extension Book {
// Nested enum -- genres
enum Genre: String {
case fiction = "Fiction"
case nonfiction = "Non-fiction"
case science = "Science"
case history = "History"
}
// Nested struct -- reading stats
struct ReadingStats {
var pagesRead: Int
var totalPages: Int
var progress: Double {
totalPages > 0 ? Double(pagesRead) / Double(totalPages) * 100 : 0
}
}
// Convenience initializer -- create from a JSON dictionary
init?(from dict: [String: Any]) {
guard let title = dict["title"] as? String,
let author = dict["author"] as? String,
let year = dict["year"] as? Int else {
return nil
}
self.init(title: title, author: author, year: year)
}
}
// Usage
let book1 = Book(title: "Swift Programming", author: "Apple", year: 2024)
let genre = Book.Genre.science
print("\(book1.title) - Genre: \(genre.rawValue)")
let stats = Book.ReadingStats(pagesRead: 120, totalPages: 350)
print("Reading progress: \(String(format: "%.1f", stats.progress))%")
// Create from dictionary
let dict = ["title": "Advanced Swift", "author": "Alice", "year": 2025] as [String: Any]
if let book2 = Book(from: dict) {
print("Created from dictionary: \(book2.title) (\(book2.author), \(book2.year))")
}
Output:
TEXT 📖 Somente leituraSwift Programming - Genre: Science Reading progress: 34.3% Created from dictionary: Advanced Swift (Alice, 2025)
6. Exemplo Completo: Sistema de Gerenciamento de Usuários (Extension + Controle de Acesso)
// ============================================
// Full example: User management system
// Features: comprehensive use of extension + access control
// ============================================
import Foundation
// 1. Core user type
public class User {
public let id: Int
public let name: String
public let email: String
// Publicly readable, internally writable
public private(set) var score: Int = 0
public private(set) var isActive: Bool = true
// Accessible within the module
internal var lastLogin: Date?
// Private
private var loginHistory: [Date] = []
public init(id: Int, name: String, email: String) {
self.id = id
self.name = name
self.email = email
}
public func login() {
let now = Date()
lastLogin = now
loginHistory.append(now)
isActive = true
}
public func addScore(_ points: Int) {
// Internal validation
guard points > 0 else { return }
score += points
}
}
// MARK: - Description Extension
extension User {
public func description() -> String {
return "User(\(id): \(name), score: \(score), active: \(isActive))"
}
// Determine based on last login time
public var isOnline: Bool {
guard let last = lastLogin else { return false }
return Date().timeIntervalSince(last) < 300
}
}
// MARK: - Activity Log Extension
extension User {
// fileprivate -- accessible only within this file
fileprivate func generateActivityReport() -> String {
return """
User Report - \(name)
=================
Score: \(score)
Active: \(isActive)
Last Login: \(lastLogin?.description ?? "Never")
"""
}
public func printReport() {
print(generateActivityReport())
}
}
// 3. Usage example
let user = User(id: 1, name: "Alice", email: "alice@example.com")
user.login()
user.addScore(50)
user.addScore(30)
print(user.description())
print("Online: \(user.isOnline)")
user.printReport()
> Saída:
> ```text
> User(1: Alice, score: 80, active: true)
> Online: true
> User Report - Alice
> =================
> Score: 80
> Active: true
> Last Login: 2026-07-30 12:00:00 +0000
> ```
---
## ❓ Perguntas Frequentes
> **P: Qual é a diferença entre extension e herança?** **R: Extensões adicionam funcionalidade a *tipos existentes* (incluindo tipos do sistema como String e Date) sem criar um novo tipo. A herança só funciona com suas próprias classes e é usada para alterar o comportamento. Extensões não podem adicionar propriedades armazenadas; a herança pode.**
> **P: Posso adicionar propriedades armazenadas em uma extension?** **R: Não. Extensões só podem adicionar propriedades computadas, métodos, inicializadores, subscritos e tipos aninhados. Propriedades armazenadas devem estar na definição original do tipo. Esta é uma decisão de design da linguagem Swift -- para evitar quebrar o layout de memória dos tipos existentes.**
> **P: Qual é o nível de acesso padrão?** **R: `internal`. Se você não escrever nenhum modificador de controle de acesso, o padrão é `internal` -- acessível dentro do módulo atual. Para desenvolvimento de App (módulo único), `internal` equivale a `public`. Para desenvolvimento de Framework, você deve escrever explicitamente `public` ou `open` para expor a módulos externos.**
> **P: Qual é a diferença entre open e public?** **R: `public` permite acesso de módulo externo, mas não permite herança ou sobrescrita. `open` permite que módulos externos herdem classes e sobrescrevam métodos. Apenas classes e métodos de classe podem usar `open`. Princípio de segurança: não use `open` a menos que necessário.**
> **P: Quando devo dividir o código em extensões?** **R: Quando a definição de um tipo ficar grande (mais de 200 linhas), agrupe por funcionalidade em múltiplas extensões (cada uma com um comentário MARK). Padrão comum: definição principal, conformidade com protocolo, métodos utilitários, inicializadores de conveniência. Uma preocupação por extensão.**
---
## 📖 Resumo
- `extension` pode adicionar propriedades computadas, métodos, inicializadores, subscritos e tipos aninhados a tipos existentes
- Extensões não podem adicionar propriedades armazenadas ou sobrescrever métodos existentes
- Estar em conformidade com protocolos via extensões é o padrão de organização de código padrão do Swift
- Cinco níveis de acesso: open > public > internal > fileprivate > private
- O padrão `public private(set)` permite "publicamente legível, privadamente gravável"
- Extensões no mesmo arquivo podem acessar membros `private`
---
## 📝 Exercícios
1. **Básico:** Adicione uma `extension` a `Int` com uma propriedade computada `aoQuadrado` e uma propriedade computada `ehPar`. Teste `4.aoQuadrado` (exibe 16) e `3.ehPar` (exibe false).
2. **Intermediário:** Adicione uma `extension` a `Date` com um método `dataFormatada` (retorna uma string formatada "yyyy-MM-dd") e uma propriedade computada `ehHoje`. Use `extension` para conformidade com `Comparable` se ainda não estiver em conformidade.
3. **Desafio:** Crie uma classe `ArmazenamentoSeguro` que usa controle de acesso para gerenciar dados sensíveis. Requisitos: métodos `public` `salvar(chave:valor:)` e `ler(chave:)`; método `internal` de limpeza de cache; lógica `private` de criptografia/descriptografia; método `fileprivate` de logging. Use extensões para agrupar conformidade com protocolo e métodos utilitários em diferentes blocos MARK.