Swift y Objective-C

KVO al detalle: isa-swizzling

Al añadir un observador, el runtime sustituye dinámicamente la clase del objeto por una subclase. Aquí se explica isa-swizzling, por qué class miente y cuándo no hay notificaciones sin pasar por el setter.

4 min de lectura
Imagen de portada de KVO al detalle: isa-swizzling

KVO (Key-Value Observing) parece sencillo. Al registrarlo con addObserver, recibes una notificación cada vez que cambia la propiedad.

En el artículo relacionado Método Swizzling en Objective-C: guía completa encontrarás los conceptos de base y casos de aplicación relacionados.

Pero ¿no resulta extraño? Aunque no añadieras código de notificación al setter, ¿cómo sabe el runtime que cambió el valor?

La respuesta es contundente. Al añadir un observador, el runtime sustituye silenciosamente la clase del objeto.

La técnica se llama isa-swizzling. Si el method swizzling del artículo anterior cambiaba la «fila» de una tabla de conexiones, aquí se reemplaza la «clase» a la que pertenece el objeto.


Qué ocurre al ejecutar addObserver

Person Al añadir un observador, el runtime hace lo siguiente.

  1. Crea dinámicamente la subclase NSKVONotifying_Person****
  2. Sobrescribe el setter **** de la propiedad observada, con una versión que inserta notificaciones antes y después del cambio.
  3. Sustituye el puntero isa **** del objeto por la nueva subclase.

Después, al ejecutar person.name = @"Kim", el flujo de llamadas es el siguiente.

// NSKVONotifying_Person pseudocódigo del  setter sobrescrito
- (void)setName:(NSString *)name {
    [self willChangeValueForKey:@"name"];
    [super setName:name];             //  ejecución del  setter  original
    [self didChangeValueForKey:@"name"];  //  notificar al observador
}

El contenido de memoria no cambia; solo cambia la clase a la que pertenece, así que el código existente no nota nada. Al eliminarse el último observador, isa vuelve a la clase original.

Diagrama del flujo en que KVO crea la subclase dinámica NSKVONotifying_Person y sustituye isa del objeto Person
Orden en que KVO crea la subclase dinámica y sustituye isa

La clase miente: class vs object_getClass

Aquí aparece un detalle interesante. Al preguntar la clase de un objeto observado, hay dos respuestas posibles.

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

El runtime también sobrescribe -class en la subclase dinámica para que **** responda la clase original. Así oculta los detalles de implementación y evita confundir al desarrollador.

object_getClass() lee directamente el puntero isa, por lo que muestra la clase real. Si los valores difieren durante la depuración, el objeto está siendo observado.


Sin pasar por el setter, no hay notificación

Al entender isa-swizzling, la conocida limitación de KVO resulta lógica.

person.name = @"Kim";       //  notificación — mediante el  setter  sobrescrito
person->_name = @"Kim";     //  sin notificación — ivar  modificación directa, setter sin pasar por él

El origen de la notificación KVO es el setter sobrescrito. Modificar directamente el ivar evita esa ruta y no ocurre nada. Es una razón práctica para recomendar la sintaxis de punto al acceder a propiedades en Objective-C, pues llama al setter internamente.

Si debes cambiar el valor sin usar el setter, puedes envolverlo en una notificación manual.

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

Para desactivar las notificaciones automáticas de una propiedad, devuelve NO desde automaticallyNotifiesObserversForKey:.


Precauciones prácticas

La gestión del ciclo de vida de la observación sigue siendo importante. Debes conservar el token NSKeyValueObservation devuelto por la API basada en bloques mientras necesites observar (Documentación de KVO de Apple).

Al invalidar o liberar el token al finalizar la observación, también se elimina el registro. Si mantienes la API basada en cadenas, debes coordinar manualmente el ciclo de vida de addObserver y removeObserver (Documentación de KVO de Apple).

Mezclar KVO y method swizzling en una misma clase causa problemas de orden. Si KVO se añade después del swizzling, la subclase dinámica envuelve el setter modificado; en el orden inverso, sus supuestos pueden no coincidir.

Cuando dos técnicas que manipulan el runtime se encuentran en un objeto, la depuración se vuelve mucho más difícil.

En Swift, @objc dynamic es obligatorio.

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

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

Esto se debe a que isa-swizzling requiere que la llamada al setter pase por la ruta de mensajes de Objective-C.

Desde Swift 4 se usa la API basada en bloques observe(_:options:changeHandler:). Al liberarse el token NSKeyValueObservation devuelto, también se elimina automáticamente el registro. Using Key-Value Observing in Swift

Así se evitan la mayoría de los problemas de la era de los keyPath basados en cadenas.

Comparación entre el objeto Person antes de isa-swizzling de KVO y la subclase dinámica NSKVONotifying_Person
Por fuera es Person, pero la clase del runtime es NSKVONotifying_Person

Resumen

  • Al registrar un observador, KVO crea dinámicamente la subclase **NSKVONotifying_ y sustituye isa **
  • El origen de la notificación es el setter ** sobrescrito **, con willChange/didChange insertados.
  • -class está manipulado para responder la clase original; la real se ve con object_getClass().
  • Modificar directamente el ivar evita el setter y no genera notificaciones; si hace falta, notifica manualmente con willChange/didChange.
  • En Swift, @objc dynamic y la API observe basada en bloques son el estándar; liberar el token elimina el registro.

Al incluir method swizzling e isa-swizzling, se entiende el runtime de Objective-C: tanto la tabla de métodos como la clase del objeto son datos que pueden cambiar en runtime. Esa flexibilidad origina gran parte de la magia de Cocoa.

Seguir leyendo

Fuentes y verificación

  • Using Key-Value Observing in SwiftApple · Documentación oficial · Consultado 17 de agosto de 2026Respalda: Registro de KVO en Swift, notificaciones de cambios y ciclo de vida del token de observación