Swift: Mergulho nas Propriedades do Swift
As propriedades associam valores aos tipos Swift. Além das propriedades armazenadas básicas, o Swift oferece propriedades computadas, observadores de propriedade, carregamento preguiçoso e outros recursos poderosos que dão controle preciso sobre o comportamento de leitura e escrita das propriedades.
1. O Que Você Vai Aprender
- Diferenças e uso de propriedades armazenadas vs computadas
- Monitorando mudanças de propriedade com observadores
willSetedidSet - Otimizando desempenho e uso de recursos com carregamento
lazy - Compartilhando dados no nível do tipo com propriedades de tipo
static - Conceito básico de wrappers de propriedade
2. Uma História Real de um Desenvolvedor Mobile
(1) Problema: Downloads de Avatar de Usuário da Rede a Cada Exibição
Bob implementou uma página de perfil de usuário que busca o avatar da rede toda vez que é exibida:
class UserProfile {
var avatarUrl: String
func displayAvatar() {
// Downloads avatar from network every time
downloadImage(from: avatarUrl) { image in
// Display image
}
}
}
Toda vez que o perfil é aberto, o avatar é baixado novamente. Se o usuário navega para dentro e para fora repetidamente, a mesma requisição de rede é executada várias vezes, desperdiçando largura de banda e carregando lentamente.
(2) Solução: Carregamento Preguiçoso + Propriedades Computadas
class UserProfile {
var avatarUrl: String
lazy var cachedAvatar: UIImage? = {
// Executes only once — downloads on first access
return downloadImage(from: avatarUrl)
}()
}
Combinado com propriedades computadas para cálculo em tempo real, o carregamento preguiçoso executa apenas uma vez — acessos subsequentes retornam o valor em cache diretamente.
(3) Benefício: Carregamento Sob Demanda, Cache Automático
| Dimensão | Download Direto | Carregamento Preguiçoso |
|---|---|---|
| Momento do carregamento | Todo acesso | Primeiro acesso |
| Requisições duplicadas | Baixa toda vez | Baixa uma vez |
| Uso de memória | Carregado mesmo quando não exibido | Alocado sob demanda |
| Velocidade de inicialização | Lenta (pré-carrega dados irrelevantes) | Rápida (inicialização adiada) |
| Complexidade do código | Gerenciamento manual de cache | Swift gerencia automaticamente |
3. Propriedades Armazenadas e Propriedades Computadas
(1) Propriedades Armazenadas
Uma propriedade armazenada é uma constante ou variável armazenada como parte de uma instância de classe ou struct:
graph TB
A[Tipos de Propriedade] --> B[Propriedade Armazenada]
A --> C[Propriedade Computada]
B --> D["let propriedade armazenada constante"]
B --> E["var propriedade armazenada variável"]
C --> F["getter — calculado na leitura"]
C --> G["setter — processado na escrita"]
| Característica | Propriedade Armazenada | Propriedade Computada |
|---|---|---|
| Memória | Consome memória da instância | Não consome memória da instância |
| Leitura/Escrita | Leitura/escrita direta | Via getter/setter |
| Tipo | let/var | apenas var |
| Observadores | Suporta willSet/didSet | Não suportado |
| Inicialização | Deve ser inicializada ou atribuída no init | Sem necessidade de inicialização |
▶ Exemplo: Propriedades Armazenadas
// ============================================
// Basic stored property usage
// ============================================
struct User {
let id: Int // Constant stored property
var name: String // Variable stored property
var email: String
}
var user = User(id: 1, name: "Alice", email: "alice@example.com")
user.name = "Alice Smith" // Variable property can be modified
// user.id = 2 // Compile error! Constant property cannot be modified
print("\(user.name) (\(user.email))")
Saída:
TEXT 📖 Somente leituraAlice Smith (alice@example.com)
(2) Propriedades Computadas
Uma propriedade computada não armazena um valor; ela calcula e retorna um valor toda vez que é acessada:
// ============================================
// Computed property — Celsius/Fahrenheit conversion
// ============================================
struct Temperature {
var celsius: Double
// Computed property — calculates fahrenheit from celsius
var fahrenheit: Double {
get {
return celsius * 9 / 5 + 32
}
set(newFahrenheit) {
celsius = (newFahrenheit - 32) * 5 / 9
}
}
}
var temp = Temperature(celsius: 25)
print("Celsius: \(temp.celsius)°C")
print("Fahrenheit: \(temp.fahrenheit)°F")
// Setting fahrenheit indirectly modifies celsius
temp.fahrenheit = 100
print("After setting to 100°F:")
print("Celsius: \(temp.celsius)°C")
Saída:
TEXT 📖 Somente leituraCelsius: 25°C Fahrenheit: 77.0°F After setting to 100°F: Celsius: 37.77777777777778°CDica: Propriedades computadas somente leitura podem omitir
gete as chaves:var dobrado: Int { valor * 2 }.
4. Observadores de Propriedade
Os observadores de propriedade monitoram as mudanças em uma propriedade armazenada, disparando antes e depois da alteração do valor.
| Observador | Momento | Parâmetro | Usos Comuns |
|---|---|---|---|
willSet |
Antes do valor ser armazenado | newValue |
Validação pré-atualização, registro |
didSet |
Depois do valor ser armazenado | oldValue |
Atualizações de UI, sincronização de dados |
▶ Exemplo: willSet e didSet
// ============================================
// Property observers monitoring score changes
// ============================================
class Player {
var name: String
var score: Int = 0 {
willSet {
print("Score will change from \(score) to \(newValue)")
}
didSet {
print("Score changed from \(oldValue) to \(score)")
if score > 100 {
print("Congrats \(name) on the high score!")
}
}
}
var level: Int {
// Read-only computed property based on score
switch score {
case 0..<50: return 1
case 50..<100: return 2
default: return 3
}
}
init(name: String) {
self.name = name
}
}
let player = Player(name: "Alice")
player.score = 60
print("Level: \(player.level)")
player.score = 120
print("Level: \(player.level)")
Saída:
TEXT 📖 Somente leituraScore will change from 0 to 60 Score changed from 0 to 60 Score will change from 60 to 120 Score changed from 60 to 120 Congrats Alice on the high score! Level: 3Aviso: Observadores de propriedade não podem ser usados em propriedades computadas, pois propriedades computadas não têm valor armazenado para "observar". Não defina a mesma propriedade dentro de
willSetoudidSet— isso causará um laço infinito.
5. Carregamento Preguiçoso e Propriedades de Tipo
(1) Carregamento lazy
Um lazy var é inicializado apenas quando a propriedade é acessada pela primeira vez, ideal para propriedades caras de criar ou nem sempre necessárias:
graph LR
A["Declarar propriedade lazy var"] --> B["Instância criada"]
B --> C["propriedade não inicializada"]
C --> D["Primeiro acesso à propriedade"]
D --> E["Executar closure de inicialização"]
E --> F["Acessos subsequentes: retornar valor em cache"]
| Cenário | Não-lazy | Lazy |
|---|---|---|
| Momento da inicialização | Na criação da instância | No primeiro acesso |
| Leitura de arquivo grande | Carregado independente do uso | Carregado apenas quando necessário |
| Requisições de rede | Pré-carregadas | Carregamento sob demanda |
| Cálculo complexo | Calculado imediatamente | Cálculo adiado |
▶ Exemplo: Configuração com Carregamento Preguiçoso
// ============================================
// lazy loading: Database configuration
// ============================================
class DatabaseManager {
let configFile: String
// Lazy — only initializes connection when a query is executed
lazy var connection: String = {
print("Establishing database connection (one-time only)...")
// Simulate establishing a connection
return "Connected to \(configFile)"
}()
init(configFile: String) {
self.configFile = configFile
}
func query(_ sql: String) {
print("Using connection: \(connection) executing query: \(sql)")
}
}
let db = DatabaseManager(configFile: "app.db")
print("DatabaseManager created, connection not initialized")
db.query("SELECT * FROM users")
db.query("SELECT * FROM orders")
// Second access to connection does not re-initialize
Saída:
TEXT 📖 Somente leituraDatabaseManager created, connection not initialized Establishing database connection (one-time only)... Using connection: Connected to app.db executing query: SELECT * FROM users Using connection: Connected to app.db executing query: SELECT * FROM ordersAviso:
lazydeve ser usado comvar(letnão pode ser inicializado preguiçosamente). Propriedades lazy não são thread-safe — o primeiro acesso simultâneo de múltiplas threads pode causar múltiplas inicializações.
(2) Propriedades de Tipo (static)
Propriedades de tipo pertencem ao próprio tipo, não a uma instância particular. Todas as instâncias compartilham os mesmos dados:
// ============================================
// static type properties
// ============================================
struct AppConfig {
static let appName = "MySwiftApp"
static var version = "1.0"
static var launchCount = 0
static func incrementLaunch() {
launchCount += 1
}
}
// Access directly via type name, no instance needed
print(AppConfig.appName)
AppConfig.version = "1.1"
AppConfig.incrementLaunch()
AppConfig.incrementLaunch()
print("Version: \(AppConfig.version), Launches: \(AppConfig.launchCount)")
Saída:
TEXT 📖 Somente leituraMySwiftApp Version: 1.1, Launches: 2
6. Exemplo Completo: Sistema de Gerenciamento de Perfil
// ============================================
// Complete example: Profile management system
// Features: Stored/computed properties + observers + lazy + static
// ============================================
import Foundation
// 1. Global configuration (type properties)
struct Config {
static let maxAvatarSizeMB = 5.0
static var apiBaseURL = "https://api.example.com"
static var userCount = 0
}
// 2. User profile
class UserProfile {
// Stored properties
let id: Int
var name: String {
didSet {
print("Name updated: \(oldValue) -> \(name)")
}
}
var avatarUrl: String
// Computed property — generate initials from name
var initials: String {
name.split(separator: " ").compactMap { $0.first }.map { String($0) }.joined()
}
// Lazy — compute age description on first access
lazy var ageDescription: String = {
print("Computing age description for the first time...")
return "\(name)'s profile"
}()
// Property observer
var email: String {
willSet {
print("Updating email...")
}
didSet {
print("Email updated to \(email)")
}
}
init(id: Int, name: String, email: String, avatarUrl: String) {
self.id = id
self.name = name
self.email = email
self.avatarUrl = avatarUrl
Config.userCount += 1
}
deinit {
Config.userCount -= 1
}
}
// 3. Usage
let profile = UserProfile(
id: 1,
name: "Alice Johnson",
email: "alice@example.com",
avatarUrl: "https://example.com/avatar.jpg"
)
print("Initials: \(profile.initials)")
print("Age: \(profile.ageDescription)")
print("Active users: \(Config.userCount)")
// Triggers observers
profile.email = "alice@newdomain.com"
profile.name = "Alice Smith"
Saída:
TEXT 📖 Somente leituraInitials: AJ Computing age description for the first time... Age: Alice Johnson's profile Active users: 1 Updating email... Email updated to alice@newdomain.com Name updated: Alice Johnson -> Alice Smith
❓ Perguntas Frequentes
P: Qual é a diferença entre propriedades lazy e computadas? R: Propriedades lazy calculam e armazenam o resultado no primeiro acesso (retornando o valor em cache depois); propriedades computadas recalculam toda vez (sem cache). Lazy é bom para custos únicos; computada é boa para valores dinâmicos. P: Os observadores de propriedade disparam dentro de inicializadores? R: Não. Quando uma propriedade é definida em
init,willSetedidSetnão são chamados. Eles só disparam na atribuição a uma instância já inicializada. Esta é uma decisão de design do Swift. P: Uma propriedade computada pode não ter setter? R: Sim. Propriedades computadas somente leitura podem omitir a palavra-chavegete as chaves. Por exemplo:var dobrado: Int { valor * 2 }. P: Propriedades de tipo podem ser sobrescritas por subclasses? R: Propriedadesstaticnão podem ser sobrescritas. Se você precisar de uma propriedade de tipo que possa ser sobrescrita em uma classe, use a palavra-chaveclassem vez destatic. P: Uma propriedade pode ter tanto um observador quanto um setter? R: Não. Observadores de propriedade só se aplicam a propriedades armazenadas. Mudanças em propriedades computadas são tratadas no setter e não precisam de observadores. Os dois são mutuamente exclusivos.
📖 Resumo
- Propriedades armazenadas armazenam valores diretamente; propriedades computadas fornecem valores indiretamente via getters/setters
willSetdispara antes do valor ser armazenado;didSetdispara depois- Propriedades
lazy varsão inicializadas no primeiro acesso, ideais para criação de recursos caros - Propriedades
staticpertencem ao próprio tipo e são compartilhadas por todas as instâncias - Propriedades computadas somente leitura podem omitir
gete as chaves de retorno - Observadores de propriedade não disparam durante
init
📝 Exercícios
- Básico: Defina uma struct
Circulocom uma propriedade armazenadaraioe uma propriedade computadaarea(retornando a área, πr²). Crie um círculo com raio 5 e imprima a área. - Intermediário: Crie uma classe
CartaoBancariocom uma propriedade armazenadasaldoe um observadordidSet— imprima um aviso quando o saldo cair abaixo de 0 e um alerta de saldo baixo quando abaixo de 10. Implemente os métodossacar(_:)edepositar(_:). - Desafio: Implemente uma classe
Loggerusando propriedades de tipostaticpara armazenar o nível de log e o array de histórico de logs. Forneça um métodostaticlog(_:nivel:)que adiciona entradas ao array de histórico, e uma propriedade computadastaticlogFormatadoque retorna o texto de log formatado. UsedidSetpara verificar automaticamente o limite de contagem de entradas quando novos logs são adicionados.