Ao adicionar o Firebase Analytics, eventos de entrada em telas são registrados automaticamente. Mesmo sem inserir uma única linha de código em viewDidAppear.
Isso é possível graças ao swizzling de métodos (Method Swizzling). A técnica substitui toda a implementação de um método em tempo de execução. É um caso emblemático da flexibilidade do runtime de Objective-C, mas também uma faca de dois gumes que pode abrir as portas do inferno da depuração quando usada incorretamente.
Como explicado no artigo sobre objc_msgSend, uma chamada de método em Objective-C funciona buscando um IMP (ponteiro de função) pelo seletor e saltando até ele. O swizzling modifica em tempo de execução justamente essa tabela seletor→IMP.
Princípio: trocar as duas linhas da tabela
A lista de métodos de uma classe é uma tabela de mapeamento “seletor → IMP”. method_exchangeImplementations troca entre si os IMPs de duas entradas.
| Seletor | IMP antes da troca | IMP depois da troca |
|---|---|---|
viewDidAppear: |
Implementação original | minha implementação |
swz_viewDidAppear: |
Minha implementação | implementação original |
Depois da troca, quando o sistema chama viewDidAppear:, minha implementação é executada. A original não desapareceu; apenas se mudou para trás do nome swz_viewDidAppear:.
Implementação convencional completa
Na prática, a forma com todos os mecanismos de segurança normalmente usados é esta.
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = [self class];
SEL originalSEL = @selector(viewDidAppear:);
SEL swizzledSEL = @selector(swz_viewDidAppear:);
Method original = class_getInstanceMethod(cls, originalSEL);
Method swizzled = class_getInstanceMethod(cls, swizzledSEL);
BOOL added = class_addMethod(cls, originalSEL,
method_getImplementation(swizzled),
method_getTypeEncoding(swizzled));
if (added) {
class_replaceMethod(cls, swizzledSEL,
method_getImplementation(original),
method_getTypeEncoding(original));
} else {
method_exchangeImplementations(original, swizzled);
}
});
}
- (void)swz_viewDidAppear:(BOOL)animated {
[self swz_viewDidAppear:animated]; // Não é recursivo! Explicação abaixo
NSLog(@"entrada na tela: %@", NSStringFromClass([self class]));
}
@end
Há dois pontos fáceis de confundir neste código.
Primeiro, [self swz_viewDidAppear:animated]não é recursivo. Quando este código executa, os IMPs já foram trocados, então o seletor swz_ está ligado à implementação original. Em outras palavras, esta linha é a “chamada original”. Se você omiti-la, toda a lógica de transição de tela desaparece; na prática, ela é obrigatória.
Segundo, por que não chamar method_exchangeImplementationsdiretamente e tentar class_addMethodprimeiro? Porque o método-alvo pode estar implementado apenas na superclasse, não nessa classe. Um exchange direto alteraria o método da superclasse e afetaria todas as subclasses que a herdam. Se class_addMethod for bem-sucedido, o original é “adicionado novamente a esta classe”, e a troca fica limitada com segurança à própria classe.
O motivo para usar +load e dispatch_once é simples: swizzling altera estado global e deve ocorrer uma única vez, no momento mais cedo possível do ciclo de vida do app. O momento exato de chamada de +load será tratado separadamente no próximo artigo (+load vs +initialize).
Efeito colateral silencioso: _cmd mente
Se você imprimir _cmd (o seletor atual) dentro de um método submetido a swizzling, obterá viewDidAppear: em vez de swz_viewDidAppear:. A implementação mudou, mas o seletor no caminho da chamada continua igual.
Normalmente isso não causa problemas, mas misturá-lo com código que usa _cmd como chave — por exemplo, o padrão de usar _cmd como chave de Associated Objects — pode gerar bugs sutis. Se a implementação original dependia internamente de _cmd, o comportamento pode mudar.
Até onde é aceitável usar
As situações que justificam swizzling são bastante restritas.
- Instrumentação comum a todas as telas: quando SDKs de Analytics ou logging coletam automaticamente entradas em telas e toques em botões
- Contorno de bugs de terceiros ou do sistema: para corrigir temporariamente o comportamento de um framework sem código-fonte disponível
- Ferramentas de depuração para desenvolvimento: para rastrear todas as chamadas de um método específico
Por outro lado, estas situações são sinais de perigo.
- Se várias bibliotecas fazem swizzling do mesmo método, a ordem de execução depende da ordem de carregamento; se uma omitir a chamada original, todas as outras quebram
- Se métodos
swz_se infiltrarem no stack trace, fica difícil interpretar os relatórios de crash - Swizzling que depende de implementações internas do sistema pode quebrar com uma única atualização do OS
Por isso, se o problema puder ser resolvido com subclassing, proxy de delegate ou composição, essas opções sempre vêm primeiro. O swizzling deve ficar reservado como último recurso, “quando estruturalmente não houver outra alternativa”.
E no Swift?
Métodos puros de Swift usam despacho estático (ou vtable), portanto esta técnica não funciona. Para aplicar swizzling, o método-alvo precisa estar exposto ao runtime de Objective-C.
class Tracker: NSObject {
@objc dynamic func fire() { }
}
É preciso adicionar @objc dynamic para que a chamada siga pelo caminho objc_msgSend; só então é possível substituir a tabela de conexões. As classes de UIKit são baseadas em Objective-C, então o swizzling ainda funciona, mas seu espaço diminui à medida que avançamos para o mundo de SwiftUI.
Resumo
- Swizzling troca em tempo de execução a tabela seletor→IMP — a implementação original não desaparece; muda-se para trás de outro seletor
- A chamada de
[self swz_...]dentro da implementação submetida a swizzling não é recursiva; é a chamada original - Tentamos
class_addMethodprimeiro para não alterar o método da superclasse - Usamos
+load+dispatch_oncepara executar apenas uma vez durante o ciclo de vida do app - Como há risco real de
_cmdinconsistência, conflitos entre bibliotecas e atualizações do OS, usamos como último recurso - No Swift, apenas métodos marcados com
@objc dynamicpodem sofrer swizzling
No próximo artigo, vamos dissecar outra magia do runtime facilmente confundida com swizzling: o isa-swizzling do KVO (Key-Value Observing). Não se trata de trocar um método, mas de substituir a própria classe.

