← Back to list

Você pode estar prejudicando a performance do seu aplicativo iOS

Durante anos, nós, desenvolvedores iOS repetimos as mesmas frases como se fossem leis da física:

Jonatan Medina · 2026-02-17 19:53 · 1 claps · 5.5 min read
#swfit #swiftui #ios #ios-development #software-development
Open on Medium ↗
Wiki topics: SEO · SEO & SEM 📱 · Mobile Development 🔓 · Open Source

Você pode estar prejudicando a performance do seu aplicativo iOS

Durante anos, nós, desenvolvedores iOS repetimos as mesmas frases como se fossem leis da física:

“Structs são sempre mais rápidas que classes.” “Closures são vilãs da performance.” “Marque tudo como lazy.” “@Published é lento demais para tempo real.”

Se você repete ou acredita nisso, não se culpe, esses conselhos não surgiram assim do nada. Eles fizeram muito sentido anos atrás.

O problema é que tudo muda, tudo evolui, e o Swift não é diferente, o Swift amadureceu muito e, em 2026, seguir essas "boas práticas" cegamente pode estar deixando seu app mais lento, não mais rápido.

Este artigo é um convite para questionar algumas "verdades pétreas" do desenvolvimento iOS e perceber que performance hoje é medida. Não é uma opinião.

Mito #1 — “Structs são sempre mais performáticas que Classes”

Structs ganharam fama porque evitam ARC e incentivam imutabilidade. Tudo isso ainda é verdade. Mas hoje a realidade é mais complexa, mas o que mudou?

Bom, primeiro é que o ARC moderno é extremamente eficiente e o que quase ninguém comenta é: structs grandes copiam dados. Mesmo com Copy-on-Write, copiar arrays, dicionários ou imagens tem custo. Em muitos cenários, uma classe bem definida, com estado compartilhado e ciclo de vida claro, é mais eficiente e semanticamente correta.

Um struct pequeno e imutável é excelente. Mas structs grandes, com arrays, dicionários ou imagens, podem gerar custos invisíveis.

struct LargeCache {
    var images: [String: UIImage]
}

Cada cópia desse struct carrega um custo significativo. Em contrapartida, uma classe bem definida, imutável ou com estado compartilhado controlado, pode ser mais eficiente e semanticamente correta.

Agora pense nesse uso comum:

func loadImages(cache: ImageCache) {
    // ...
}

let cache = ImageCache(images: loadAllImages())
loadImages(cache)

*Dictionary* é Copy-on-Write, copiar pode parecer barato, mas no momento em que alguém muda, o Swift:

  1. duplica toda a estrutura
  2. aloca nova memória
  3. copia referências das imagens

Isso é invisível no código, mas caro em runtime. Agora imagine: múltiplas threads, múltiplas views SwiftUI, mutações ocasionais. Pois é você criou um custo explosivo e imprevisível.

E se usarmos classe com estado compartilhdo controlado? Pois esse cache não é um valor, é um recurso compartilhado, semanticamente ele deveria ser um referência.

final class ImageCache {

    private var images: [String: UIImage] = [:]

    func image(for key: String) -> UIImage? {
        images[key]
    }

    func insert(_ image: UIImage, for key: String) {
        images[key] = image
    }
}

Por que isso é melhor?

  • Nenhuma cópia implícita
  • Memória compartilhada de forma explícita
  • Mutação previsível
  • ARC moderno lida muito bem com isso
  • Intenção clara: isso é um cache

Em outras palavras, a pergunta certa não é: "O que é mais rápido, structs ou classes?" e sim, algo como: “Isso representa um valor ou um recurso compartilhado?”

Struct = valor Class = identidade / recurso compartilhado

Se algo:

  • cresce com o tempo
  • é mutável
  • é usado por múltiplos consumidores

➡️ classe é a escolha correta.

Mito #2 — “Lazy sempre melhora o tempo de inicialização”

Marcar tudo como lazy virou automático, a lógica parece boa: "Vou só utilizar quando realmente precisar"

Mas isso não elimina o custo, ele apenas esconde. Além disso, vale lembrar que, apesar de pequeno, propriedades lazy tem overhead, o custo aparece em momentos imprevisíveis e em alguns casos, podem causar travamentos durante interações com usuário. Vale frisar que o problema não é o custo em si, mas quando ele ocorre.

Não é por acaso que o UIKit inicializa muita coisa de forma eager. Ele prefere um pequeno custo no launch que inconsistências depois.

Podemos então dizer, use lazy apenas quando:

  • O recurso é opcional
  • A inicialização é realmente cara
  • Funcionalidades raramente usadas

Mito #3 — “Interpolação de strings é lenta”

Bom, esse é um mito que deveria estar enterrado, pois desde o Swift 5, a interpolação de strings foi completamente redesenhada. Hoje ela é:

  • fortemente tipada
  • otimizada em tempo de compilação
  • mais rápida que alternativas antigas
let message = "Usuário \(userID) carregou \(items.count) itens em \(duration)ms"

Comparado a:

String(format: "Usuário %d carregou %d itens em %.2fms", userID, items.count, duration)

A segunda opção é menos eficiente, além de lento, é inseguro e é um legado do C.

Logo, use interpolação de string, e para logs críticos, prefira Logger e os_log.

Mito #4 — “Closures prejudicam performance”

Closures são uma das construções mais otimizadas do Swift moderno. Criar uma closure custa praticamente nada. O que pesa é:

  • capturar objetos grandes
  • criar retain cycles
  • usar self sem critério

Se você está micro-otimizando a criação de closures, está olhando para o lugar errado.

someAsyncCall { [weak self] result in
    self?.handle(result)
}

O foco deve estar no que a closure captura, não na existência dela.

Mito #5 — “@Published é lento para atualizações em tempo real”

Com as melhorias recentes em Combine, SwiftUI, propriedades reativas se tornaram abstrações baratas na maioria dos casos.

Para a maior parte dos apps, podemos afirmar que:

  • @Published é suficiente
  • o ganho de legibilidade compensa
  • a performance é adequada

Quando surgem problemas, quase sempre a causa é:

  • estado excessivo
  • estado mal dividido
  • views redesenhando sem necessidade.

Reatividade não é o vilão. Estado mal modelado sim.

Mas o que realmente pode impactar na performance do meu app?

Bom, podemos citar alguns:

1 — Decodificação de de imagens no main thread

❌ Errado

let image = UIImage(data: data)
imageView.image = image

O que acontece:

  • UIImage(data:) decodifica a imagem
  • Decodificação é CPU-bound
  • Executa no main thread
  • Causa frame drops e scroll travado

Isso é muito comum em listas e feeds.

✅ Correto

DispatchQueue.global(qos: .userInitiated).async {
    let image = UIImage(data: data)?.preparingForDisplay()

    DispatchQueue.main.async {
        imageView.image = image
    }
}
  • Decodifica fora da main thread
  • preparingForDisplay() evita custo na renderização

2 — Parsing de JSON no Main Actor

❌ Problema

@MainActor
func loadData() async throws {
    let data = try await fetchData()
    let result = try JSONDecoder().decode(Response.self, from: data)
    self.model = result
}

O que acontece

  • JSONDecoder é pesado
  • Está rodando no Main Actor
  • Bloqueia UI enquanto decodifica

✅ Correto

func loadData() async throws {
    let data = try await fetchData()

    let result = try await Task.detached {
        try JSONDecoder().decode(Response.self, from: data)
    }.value

    await MainActor.run {
        self.model = result
    }
}
  • Parsing fora do Main Actor
  • UI atualizada apenas no final

3 — SwiftUI renderizando views desnecessariamente

❌ Problema

struct ContentView: View {
    @State var counter = 0

    var body: some View {
        VStack {
            Text("Counter: \(counter)")
            ExpensiveView()
        }
    }
}

O que acontece

  • Toda mudança em counter
  • Reexecuta body
  • ExpensiveView é recalculada mesmo sem depender do estado

✅ Correto

struct ContentView: View {
    @State var counter = 0

    var body: some View {
        VStack {
            Text("Counter: \(counter)")
            ExpensiveView()
                .equatable()
        }
    }
}
  • SwiftUI evita redesenhar
  • Menos atualizações

4 — Falta de .equatable() e uso incorreto de .id()

❌ Problema

ForEach(items, id: \.self) { item in
    ItemView(item: item)
}

Ou pior:

ItemView(item: item)
    .id(UUID())

O que acontece

  • SwiftUI acha que tudo mudou
  • Views são destruídas e recriadas
  • Perde animações, estado interno e performance

✅ Correto

struct Item: Identifiable, Equatable {
    let id: UUID
    let name: String
}

ForEach(items) { item in
    ItemView(item: item)
}
  • Identidade estável
  • Diferenciação eficiente
  • Atualiza só o que mudou

5 — Bloqueio de Main Actor com Swift Concurrency

❌ Problema

@MainActor
func processData() {
    let result = heavyComputation()
    label.text = result
}

O que acontece

  • heavyComputation() roda no Main Actor
  • UI fica congelada
  • Scroll e animações travam

✅ Correto

func processData() async {
    let result = await Task.detached {
        heavyComputation()
    }.value

    await MainActor.run {
        label.text = result
    }
}
  • Trabalho pesado fora
  • UI atualizada de forma segura

6- Estado global mal estruturado

❌ Problema

class AppState: ObservableObject {
    @Published var user: User?
    @Published var cart: [Item] = []
    @Published var theme: Theme
}

O que acontece

  • Qualquer mudança
  • Notifica todas as views
  • SwiftUI redesenha mais do que deveria

Esse é um dos maiores assassinos de performance em SwiftUI.

✅ Correto

class UserState: ObservableObject {
    @Published var user: User?
}

class CartState: ObservableObject {
    @Published var items: [Item] = []
}

Uso segmentado:

@StateObject var cartState = CartState()
  • Menos invalidações
  • Atualizações mais localizadas
  • Melhor escalabilidade

Performance hoje em dia não é apenas sobre seguir regras — é sobre medir.

Ferramentas como:

  • Instruments
  • Time Profiler
  • Allocations
  • SwiftUI body tracing

são muito mais importantes do que qualquer “dica” genérica.

Lembre — se

UI só renderiza. Main Actor só coordena. Trabalho pesado fica fora. Estado é pequeno, local e explícito.

O que custa caro é assumir performance sem medir.


메타데이터
post_id
b48e15cd8bd0
slug
você-pode-estar-prejudicando-a-performance-do-seu-aplicativo-ios-b48e15cd8bd0
url
https://medium.com/@jonatanmedina-dev/voc%C3%AA-pode-estar-prejudicando-a-performance-do-seu-aplicativo-ios-b48e15cd8bd0
canonical_url
https://medium.com/@jonatanmedina-dev/voc%C3%AA-pode-estar-prejudicando-a-performance-do-seu-aplicativo-ios-b48e15cd8bd0
author_url
https://medium.com/@jonatanmedina-dev
status
ok
fetched_at
2026-07-13 06:23:13