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


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:

SWIFT
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

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

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

SWIFT
// ============================================
// 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 leitura
Alice 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:

SWIFT
// ============================================
// 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 leitura
Celsius: 25°C
Fahrenheit: 77.0°F
After setting to 100°F:
Celsius: 37.77777777777778°C

Dica: Propriedades computadas somente leitura podem omitir get e 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

SWIFT
// ============================================
// 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 leitura
Score 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: 3

Aviso: 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 willSet ou didSet — 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:

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

SWIFT
// ============================================
// 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 leitura
DatabaseManager 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 orders

Aviso: lazy deve ser usado com var (let nã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:

SWIFT
// ============================================
// 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 leitura
MySwiftApp
Version: 1.1, Launches: 2

6. Exemplo Completo: Sistema de Gerenciamento de Perfil

SWIFT
// ============================================
// 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 leitura
Initials: 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, willSet e didSet nã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-chave get e as chaves. Por exemplo: var dobrado: Int { valor * 2 }. P: Propriedades de tipo podem ser sobrescritas por subclasses? R: Propriedades static não podem ser sobrescritas. Se você precisar de uma propriedade de tipo que possa ser sobrescrita em uma classe, use a palavra-chave class em vez de static. 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


📝 Exercícios

  1. Básico: Defina uma struct Circulo com uma propriedade armazenada raio e uma propriedade computada area (retornando a área, πr²). Crie um círculo com raio 5 e imprima a área.
  2. Intermediário: Crie uma classe CartaoBancario com uma propriedade armazenada saldo e um observador didSet — imprima um aviso quando o saldo cair abaixo de 0 e um alerta de saldo baixo quando abaixo de 10. Implemente os métodos sacar(_:) e depositar(_:).
  3. Desafio: Implemente uma classe Logger usando propriedades de tipo static para armazenar o nível de log e o array de histórico de logs. Forneça um método static log(_:nivel:) que adiciona entradas ao array de histórico, e uma propriedade computada static logFormatado que retorna o texto de log formatado. Use didSet para verificar automaticamente o limite de contagem de entradas quando novos logs são adicionados.
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%