Por que o código de method swizzling sempre fica dentro de +load? Não dá para colocá-lo no +initialize, que parece semelhante?
+load e +initialize parecem métodos “chamados uma vez quando a classe está pronta”, mas diferem completamente no momento e na forma da chamada e nas regras de herança. Também é um tema frequente em entrevistas de Objective-C e, quando mal compreendido, é fácil ficar se perguntando: “Por que este código está sendo executado duas vezes?”. Vale a pena resumir o assunto.
Tabela comparativa rápida
+load |
+initialize |
|
|---|---|---|
| Momento da chamada | Quando a classe é carregada pelo runtime (antes de main) | Imediatamente antes de a classe receber sua primeira mensagem (lazy) |
| Forma da chamada | Chamada direta por ponteiro de função | Via objc_msgSend |
| Categorias | As implementações da classe e da categoria são chamadas separadamente | A implementação da categoria sobrescreve a implementação da classe |
| Herança | Somente a classe que implementa é chamada | É herdado — pode ser chamado várias vezes por causa das subclasses |
| Se não for usado? | Ainda assim é chamado | Nunca é chamado se a classe não for usada |
Vamos detalhar os pontos que não ficam claros apenas pela tabela.
+load: antes de main, incondicionalmente
+load é chamado quando o runtime carrega o binário que contém a classe ou categoria, ou seja, antes mesmo de a função main ser executada. A chamada ocorre mesmo que o app nunca use essa classe.
A forma da chamada é peculiar. Ele é chamado diretamente por um ponteiro de função, sem passar por objc_msgSend. Por isso, as regras normais de sobrescrita não se aplicam.
- Se uma subclasse não implementa
+load, a implementação do pai não é chamada em seu lugar. - A
+loadda classe e a+loadda categoria são chamadas separadamente — isso não é sobrescrita. - A ordem é garantida: a
+loadda classe pai vem antes da filha, e a+loadda classe vem antes da categoria.
Essas características combinam perfeitamente com swizzling. Mesmo implementando +load em uma categoria, isso não interfere na +load da classe original e é executado uma vez, com garantia, no momento mais cedo do ciclo de vida do app.
Mas há um custo. +load é cobrado diretamente do tempo de inicialização do app. Como a +load de todas as classes é executada sequencialmente antes de main, trabalhos pesados aqui atrasam a primeira tela. É por isso que a Apple recomenda há anos “evitar +load sempre que possível”. Na prática, dentro de +load, outras classes fora da imagem à qual self pertence podem ainda não ter sido carregadas, o que limita o que pode ser feito.
+initialize: logo antes da primeira mensagem, de forma lazy
+initialize adota a estratégia oposta. O runtime o chama imediatamente antes de a classe receber sua primeira mensagem. Se o app nunca usar a classe, ela nunca será chamada. É um ponto de inicialização lazy que não pesa no tempo de inicialização.
Como passa por objc_msgSend, as regras de herança se aplicam como em um método normal. É exatamente aqui que surge a famosa armadilha.
@implementation Animal
+ (void)initialize {
NSLog(@"initialize: %@", self);
}
@end
@interface Dog : Animal
@end
@implementation Dog
@end
Ao enviar a primeira mensagem para Dog, o log fica assim.
initialize: Animal
initialize: Dog
A +initialize de Animal é executada duas vezes: uma vez pela própria Animal e outra porque Dog, que não implementa +initialize, herda e executa a implementação do pai. Por isso, a implementação convencional de +initialize inclui uma verificação de classe.
+ (void)initialize {
if (self == [Animal class]) {
// Somente a inicialização que é realmente Animal responsabilidade própria
}
}
A propósito, +initialize é thread-safe por si só, pois o runtime usa um lock por classe antes de chamá-lo. Não é necessário adicionar dispatch_once.
Critérios práticos de escolha
O critério é simples.
- Tarefas que precisam ser feitas “incondicionalmente e o mais cedo possível”, como swizzling e registro de classes →
+load(mas de forma mínima) - Preparação necessária apenas quando a classe é usada →
+initialize(a verificação de self é obrigatória) - A maior parte das inicializações → na verdade, nenhuma das duas; um
dispatch_oncesingleton ou uma propriedade lazy é suficiente
Swift não tem esse dilema. Swift não oferece nada correspondente a +load. Também não existe um mecanismo oficial para inserir código executável global antes de main. Em vez disso, propriedades de tipo (static let) garantem inicialização lazy e segurança de thread no nível da linguagem, substituindo o papel de +initialize. Esse é um dos pontos em que Swift é estruturalmente vantajoso para o desempenho de inicialização do app.
Resumo
+loadé chamado antes de main, incondicionalmente, diretamente por ponteiro de função — as implementações da classe e da categoria são chamadas separadamente+initializeé chamado logo antes da primeira mensagem, de forma lazy e via msgSend — nunca é chamado em classes não usadas+initializepode ser executado várias vezes por causa da herança, portanto aif (self == [MyClass class])verificação é convencional- Por que o swizzling fica em
+load: momento mais cedo + chamada independente da categoria + execução única garantida - O uso excessivo de
+loadatrasa diretamente a inicialização do app — adie inicializações pesadas usando lazy
Ao ler este texto junto com outros artigos da série sobre runtime (objc_msgSend, encaminhamento de mensagens, swizzling e KVO), fica claro que a diferença entre +load e +initialize acaba se dividindo em um único eixo: passar ou não por msgSend. Ao entender o caminho da chamada, você não precisa decorar as regras.

