¿Por qué hay dos signos de igualdad en Swift y qué esconden?
\n\n¿Alguna vez has escrito código en Swift y te has topado con el extraño operador ===? El primer pensamiento: "¿Un error tipográfico? ¿O es una especie de super-igual?" En realidad, no es un error tipográfico, sino un mecanismo poderoso que cambia radicalmente la lógica de comparación. Si trabajas con clases, estructuras o closures, entender la diferencia entre == y === no es teoría, sino una necesidad. Sin esto, corres el riesgo de tener errores difíciles de detectar.
En este artículo, miraremos "bajo el capó" de Swift, analizaremos cómo funcionan ambos operadores y aprenderemos a elegir la herramienta correcta para cada tarea. ¿Listos? ¡Allá vamos.
\n\n¿Qué es el operador de igualdad ==?
\n\nEl operador == es tu "amigo" estándar para comparar valores. Verifica si dos objetos son iguales por su contenido. Pero hay un detalle: Swift no sabe qué significa "iguales" para tu propio tipo de dato. Por lo tanto, para usar ==, tu tipo debe cumplir con el protocolo Equatable.
Así es como se ve para los tipos incorporados:
\n\nlet a = 5\nlet b = 5\nprint(a == b) // true, porque los valores son iguales\n\nlet name1 = "Alice"\nlet name2 = "Alice"\nprint(name1 == name2) // true, las cadenas son iguales\n\nPara clases y estructuras, debes implementar Equatable tú mismo. Por ejemplo:
struct User {\n let id: Int\n let name: String\n}\n\nextension User: Equatable {\n static func == (lhs: User, rhs: User) -> Bool {\n return lhs.id == rhs.id && lhs.name == rhs.name\n }\n}\n\nlet user1 = User(id: 1, name: "John")\nlet user2 = User(id: 1, name: "John")\nprint(user1 == user2) // true, el contenido coincide\n\nObserva: == compara valores, no referencias. Para las estructuras esto es obvio, pero con las clases comienza lo interesante.
Operador de identidad ===: cuando dos objetos son lo mismo
\n\nEl operador === (triple igual) no verifica el contenido, sino la identidad de las referencias. Responde a la pregunta: "¿Apuntan dos variables al mismo objeto en memoria?"
Este operador funciona solo con tipos por referencia: clases y closures (los closures también son tipos por referencia). Para estructuras y enumeraciones simplemente no existe: el compilador dará un error.
\n\nMira el ejemplo:
\n\nclass Car {\n var model: String\n init(model: String) {\n self.model = model\n }\n}\n\nlet car1 = Car(model: "Tesla")\nlet car2 = Car(model: "Tesla")\nlet car3 = car1\n\nprint(car1 == car2) // false, si no se implementó Equatable\nprint(car1 === car2) // false, objetos diferentes\nprint(car1 === car3) // true, la misma referencia\n\nAquí car1 y car2 son dos objetos diferentes con el mismo contenido. Y car3 es simplemente otro nombre para el mismo objeto que car1. === lo muestra claramente.
Cómo funciona bajo el capó: memoria y punteros
\n\nPara entender la diferencia, hay que mirar la memoria. Cuando creas una instancia de una clase, Swift reserva un espacio en el montón (heap) y devuelve un puntero a ese espacio. La variable almacena el puntero, no el objeto en sí.
\n\n- \n
==compara el contenido a través del puntero: llama a tu implementación deEquatable, que puede revisar los campos del objeto. \n ===compara los punteros en sí: si dos punteros apuntan a la misma dirección en memoria, el resultado estrue. \n
Para las estructuras es más simple: se almacenan en la pila (stack) y se copian al asignarse. Por lo tanto, === no tiene sentido para ellas: dos estructuras diferentes siempre serán objetos distintos en memoria, incluso si su contenido es