Swift e Objective-C

KVO em detalhes: isa-swizzling

Ao adicionar um observador, o runtime substitui dinamicamente a classe do objeto por uma subclasse. Este texto explica isa-swizzling, por que class mente e quando não há notificações sem passar pelo setter.

4 min de leitura
Imagem de capa de KVO em detalhes: isa-swizzling

KVO (Key-Value Observing) parece simples. Ao registrar com addObserver, você recebe uma notificação sempre que a propriedade muda.

No artigo relacionado Method Swizzling em Objective-C: guia completo, você encontra os conceitos básicos e casos de aplicação relacionados.

Mas não é estranho? Sem adicionar código de notificação ao setter, como o runtime sabe que o valor mudou?

A resposta é direta. Ao adicionar um observador, o runtime troca silenciosamente a classe do objeto.

Essa técnica se chama isa-swizzling. Se o method swizzling do artigo anterior trocava a “linha” de uma tabela de conexões, aqui a própria “classe” do objeto é substituída.


O que acontece ao executar addObserver

Person Ao adicionar um observador, o runtime faz o seguinte.

  1. Cria dinamicamente a subclasse NSKVONotifying_Person****
  2. Sobrescreve o setter **** da propriedade observada, usando uma versão que insere notificações antes e depois da alteração.
  3. Troca o ponteiro isa **** do objeto pela nova subclasse.

Depois, ao executar person.name = @"Kim", o fluxo de chamadas fica assim.

// NSKVONotifying_Person pseudocódigo do  setter sobrescrito
- (void)setName:(NSString *)name {
    [self willChangeValueForKey:@"name"];
    [super setName:name];             //  execução do  setter  original
    [self didChangeValueForKey:@"name"];  //  notificar o observador
}

O conteúdo da memória permanece igual; apenas a classe muda, então o código existente não percebe nada. Quando o último observador é removido, isa volta à classe original.

Diagrama do fluxo em que KVO cria a subclasse dinâmica NSKVONotifying_Person e troca isa do objeto Person
Ordem em que KVO cria a subclasse dinâmica e troca isa

A classe mente: class vs object_getClass

Aqui surge um detalhe interessante. Ao perguntar a classe de um objeto observado, há duas respostas possíveis.

[person class];              // Person —  mentira
object_getClass(person);     // NSKVONotifying_Person —  verdade

O runtime também sobrescreve -class na subclasse dinâmica para que **** responda a classe original. Assim oculta detalhes de implementação e evita confundir o desenvolvedor.

object_getClass() lê diretamente o ponteiro isa, exibindo a classe real. Se os valores forem diferentes durante a depuração, o objeto está sendo observado.


Sem passar pelo setter, não há notificação

Ao entender o princípio do isa-swizzling, a famosa limitação do KVO fica evidente.

person.name = @"Kim";       //  notificação — via o  setter  sobrescrito
person->_name = @"Kim";     //  sem notificação — ivar  alteração direta, setter sem passar por ele

A origem da notificação do KVO é o setter sobrescrito. Alterar o ivar diretamente ignora esse caminho, então nada acontece. Esse é um dos motivos práticos para recomendar a sintaxe de ponto no acesso a propriedades em Objective-C, que chama o setter internamente.

Se você precisar alterar o valor sem usar o setter, envolva a operação em uma notificação manual.

[self willChangeValueForKey:@"name"];
_name = @"Kim";
[self didChangeValueForKey:@"name"];

Para desativar as notificações automáticas de uma propriedade, retorne NO em automaticallyNotifiesObserversForKey:.


Cuidados práticos

Gerenciar o ciclo de vida da observação continua sendo importante. Mantenha o token NSKeyValueObservation retornado pela API baseada em blocos enquanto a observação for necessária (Documentação de KVO da Apple).

Ao invalidar ou liberar o token ao terminar a observação, o registro também é removido. Se você mantiver a API baseada em strings, deverá sincronizar manualmente o ciclo de vida de addObserver e removeObserver (Documentação de KVO da Apple).

Misturar KVO e method swizzling na mesma classe causa problemas de ordem. Se o KVO for adicionado depois do swizzling, a subclasse dinâmica envolve o setter alterado; na ordem inversa, as suposições podem não coincidir.

Quando duas técnicas que manipulam o runtime se encontram em um objeto, a depuração fica muito mais difícil.

No Swift, @objc dynamic é obrigatório.

class Person: NSObject {
    @objc dynamic var name: String = ""
}

let observation = person.observe(\.name, options: [.new]) { _, change in
    print(change.newValue ?? "")
}

Isso acontece porque o isa-swizzling exige que a chamada do setter passe pelo caminho de mensagens do Objective-C.

A partir do Swift 4, usa-se a API baseada em blocos observe(_:options:changeHandler:). Quando o token NSKeyValueObservation retornado é liberado, o registro também é removido automaticamente. Using Key-Value Observing in Swift

Assim, a maior parte dos problemas da era dos keyPath baseados em strings é evitada.

Comparação entre o objeto Person antes do isa-swizzling do KVO e a subclasse dinâmica NSKVONotifying_Person
Por fora é Person, mas a classe do runtime é NSKVONotifying_Person

Resumo

  • Ao registrar um observador, o KVO cria dinamicamente a subclasse **NSKVONotifying_ e troca isa **
  • A origem da notificação é o setter ** sobrescrito **, com willChange/didChange inseridos.
  • -class é manipulado para responder a classe original; a classe real aparece com object_getClass().
  • Alterar o ivar diretamente ignora o setter e não gera notificações; se necessário, notifique manualmente com willChange/didChange.
  • No Swift, @objc dynamic e a API observe baseada em blocos são o padrão; liberar o token remove o registro.

Ao considerar method swizzling e isa-swizzling, o runtime do Objective-C fica claro: tanto a tabela de métodos quanto a classe do objeto são dados que podem mudar em runtime. Essa flexibilidade está na origem de grande parte da magia dos frameworks Cocoa.

Continue lendo

Fontes e verificação

  • Using Key-Value Observing in SwiftApple · Documentação oficial · Consultado 17 de agosto de 2026Evidência: Registro de KVO em Swift, notificações de alteração e ciclo de vida do token de observação