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:
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:
- duplica toda a estrutura
- aloca nova memória
- 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
selfsem 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