Différence entre == et ===

299

Dans swift, il semble y avoir deux opérateurs d'égalité: le double égal ( ==) et le triple égal ( ===), quelle est la différence entre les deux?

Fela Winkelmolen
la source

Réponses:

149

En bref:

== l'opérateur vérifie si leurs valeurs d'instance sont égales, "equal to"

=== l'opérateur vérifie si les références pointent vers la même instance, "identical to"

Longue réponse:

Les classes sont des types de référence, il est possible que plusieurs constantes et variables se réfèrent à la même instance unique d'une classe dans les coulisses. Les références de classe restent dans Run Time Stack (RTS) et leurs instances restent dans la zone Heap de la mémoire. Lorsque vous contrôlez l'égalité avec, ==cela signifie que leurs instances sont égales les unes aux autres. Il n'est pas nécessaire que la même instance soit identique. Pour cela, vous devez fournir des critères d'égalité à votre classe personnalisée. Par défaut, les classes et structures personnalisées ne reçoivent pas d'implémentation par défaut des opérateurs d'équivalence, appelés opérateurs «égal à» ==et «non égal à» !=. Pour ce faire, votre classe personnalisée doit respecter le Equatableprotocole et sa static func == (lhs:, rhs:) -> Boolfonction

Regardons l'exemple:

class Person : Equatable {
    let ssn: Int
    let name: String

    init(ssn: Int, name: String) {
        self.ssn = ssn
        self.name = name
    }

    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.ssn == rhs.ssn
    }
}

P.S.: Étant donné que ssn (numéro de sécurité sociale) est un numéro unique, vous n'avez pas besoin de comparer si leur nom est égal ou non.

let person1 = Person(ssn: 5, name: "Bob")
let person2 = Person(ssn: 5, name: "Bob")

if person1 == person2 {
   print("the two instances are equal!")
}

Bien que les références person1 et person2 pointent deux instances différentes dans la zone Heap, leurs instances sont égales car leurs numéros SSN sont égaux. Ainsi, la sortie serathe two instance are equal!

if person1 === person2 {
   //It does not enter here
} else {
   print("the two instances are not identical!")
}

===opérateur vérifie si les références pointent la même instance, "identical to". Étant donné que personne1 et personne2 ont deux instances différentes dans la zone Heap, elles ne sont pas identiques et la sortiethe two instance are not identical!

let person3 = person1

P.S: Les classes sont des types de référence et la référence de person1 est copiée dans person3 avec cette opération d'affectation, les deux références pointent donc la même instance dans la zone Heap.

if person3 === person1 {
   print("the two instances are identical!")
}

Ils sont identiques et la sortie sera the two instances are identical!

Fatih Aksu
la source
248

!==et ===sont des opérateurs d'identité et sont utilisés pour déterminer si deux objets ont la même référence.

Swift fournit également deux opérateurs d'identité (=== et! ==), que vous utilisez pour tester si deux références d'objet font toutes deux référence à la même instance d'objet.

Extrait de: Apple Inc. «The Swift Programming Language». iBooks. https://itun.es/us/jEUH0.l

aglasser
la source
49
Ouaip. Issu de ObjC, ==is isEqual:, ou l'équivalence sémantique définie par la classe. ===dans Swift est ==dans (Obj) C - égalité de pointeur, ou identité d'objet.
rickster
Les valeurs de @rickster Dont ont également un emplacement mémoire? Je suis finalement qu'ils sont quelque part dans la mémoire. Ne pouvez-vous jamais les comparer? Ou est-ce que leur emplacement de mémoire n'offre aucune valeur significative ?
Honey
2
Il y a au moins deux façons de penser à la façon dont le langage définit les types de valeur par rapport à la mémoire. La première est que chaque liaison ( varou let) d'un nom à une valeur est une copie unique - il est donc inutile de créer des pointeurs car la valeur à laquelle vous avez fait un pointeur est une valeur différente de celle que vous avez créée en premier. Un autre est que la définition de la sémantique des valeurs par Swift supprime le stockage - le compilateur est libre d'optimiser, jusqu'à et y compris ne jamais stocker votre valeur à un emplacement de mémoire accessible au-delà de la ligne où elle est utilisée (registre, encodage d'instructions, etc.).
rickster
62

Dans les deux Objective-C et Swift, les ==et !=opérateurs test pour l' égalité de valeur pour les valeurs numériques (par exemple NSInteger, NSUInteger, int, en Objective-C et Int, UInt, etc. Swift). Pour les objets (NSObject / NSNumber et les sous-classes dans Objective-C et les types de référence dans Swift), ==et !=tester que les objets / types de référence sont la même chose identique - c'est-à-dire la même valeur de hachage - ou ne sont pas la même chose identique, respectivement .

let a = NSObject()
let b = NSObject()
let c = a
a == b // false
a == c // true

Les opérateurs d' égalité d'identité de Swift , ===et !==vérifier l'égalité référentielle - et donc, devraient probablement être appelés les opérateurs d' égalité référentielle IMO.

a === b // false
a === c // true

Il convient également de souligner que les types de référence personnalisés dans Swift (qui ne sous-classent pas une classe conforme à Equatable) n'implémentent pas automatiquement les opérateurs égaux à, mais les opérateurs d' égalité d'identité s'appliquent toujours. En outre, en implémentant ==, !=est implémenté automatiquement.

class MyClass: Equatable {
  let myProperty: String

  init(s: String) {
    myProperty = s
  }
}

func ==(lhs: MyClass, rhs: MyClass) -> Bool {
  return lhs.myProperty == rhs.myProperty
}

let myClass1 = MyClass(s: "Hello")
let myClass2 = MyClass(s: "Hello")
myClass1 == myClass2 // true
myClass1 != myClass2 // false
myClass1 === myClass2 // false
myClass1 !== myClass2 // true

Ces opérateurs d'égalité ne sont pas implémentés pour d'autres types tels que les structures dans les deux langues. Cependant, des opérateurs personnalisés peuvent être créés dans Swift, ce qui vous permettrait, par exemple, de créer un opérateur pour vérifier l'égalité d'un CGPoint.

infix operator <==> { precedence 130 }
func <==> (lhs: CGPoint, rhs: CGPoint) -> Bool {
  return lhs.x == rhs.x && lhs.y == rhs.y
}

let point1 = CGPoint(x: 1.0, y: 1.0)
let point2 = CGPoint(x: 1.0, y: 1.0)
point1 <==> point2 // true
Scott Gardner
la source
3
Désolé, mais dans Obj-C, l'opérateur == ne compare PAS pour ÉGALITÉ, mais plutôt - comme C - compare les références de pointeur (identité d'objet).
Motti Shneor du
==ne teste pas l' NSNumberégalité dans Objective-C. NSNumberest NSObjectdonc un test d'identité. La raison pour laquelle cela fonctionne PARFOIS est à cause des pointeurs marqués / des littéraux d'objets mis en cache. Il échouera pour des nombres suffisamment grands et sur des appareils 32 bits lors de la comparaison de non-littéraux.
Accatyyc
45

Dans swift 3 et au-dessus

===(ou !==)

  • Vérifie si les valeurs sont identiques (les deux pointent vers la même adresse mémoire) .
  • Comparaison des types de référence .
  • Comme ==dans Obj-C (égalité de pointeur).

==(ou !=)

  • Vérifie si les valeurs sont identiques .
  • Comparaison des types de valeur .
  • Comme la valeur isEqual:par défaut dans le comportement Obj-C.

Ici, je compare trois instances (la classe est un type de référence)

class Person {}

let person = Person()
let person2 = person
let person3 = Person()

person === person2 // true
person === person3 // false
Jakub Truhlář
la source
Vous pouvez également passer outre isEqual:dans Swift:override func isEqual(_ object: Any?) -> Bool {}
Thomas Elliot
37

Il y a des subtilités avec les martinets ===qui vont au-delà de la simple arithmétique des pointeurs. Dans Objective-C, vous avez pu comparer deux pointeurs (c.-à-d. NSObject *) Avec== cela n'est plus vrai dans Swift car les types jouent un rôle beaucoup plus important pendant la compilation.

Un terrain de jeu vous donnera

1 === 2                    // false
1 === 1                    // true
let one = 1                // 1
1 === one                  // compile error: Type 'Int' does not conform to protocol 'AnyObject'
1 === (one as AnyObject)   // true (surprisingly (to me at least))

Avec les chaînes, nous devrons nous habituer à ceci:

var st = "123"                                 // "123"
var ns = (st as NSString)                      // "123"
st == ns                                       // true, content equality
st === ns                                      // compile error
ns === (st as NSString)                        // false, new struct
ns === (st as AnyObject)                       // false, new struct
(st as NSString) === (st as NSString)          // false, new structs, bridging is not "free" (as in "lunch")
NSString(string:st) === NSString(string:st)    // false, new structs
var st1 = NSString(string:st)                  // "123"
var st2 = st1                                  // "123"
st1 === st2                                    // true
var st3 = (st as NSString)                     // "123"
st1 === st3                                    // false
(st as AnyObject) === (st as AnyObject)        // false

mais vous pouvez aussi vous amuser comme suit:

var st4 = st             // "123"
st4 == st                // true
st4 += "5"               // "1235"
st4 == st                // false, not quite a reference, copy on write semantics

Je suis sûr que vous pouvez penser à des cas beaucoup plus drôles :-)

Mise à jour pour Swift 3 (comme suggéré par le commentaire de Jakub Truhlář)

1===2                                    // Compiler error: binary operator '===' cannot be applied to two 'Int' operands
(1 as AnyObject) === (2 as AnyObject)    // false
let two = 2
(2 as AnyObject) === (two as AnyObject)  // false (rather unpleasant)
(2 as AnyObject) === (2 as AnyObject)    // false (this makes it clear that there are new objects being generated)

Cela semble un peu plus cohérent avec Type 'Int' does not conform to protocol 'AnyObject', mais nous obtenons ensuite

type(of:(1 as AnyObject))                // _SwiftTypePreservingNSNumber.Type

mais la conversion explicite montre clairement qu'il pourrait y avoir quelque chose. Du côté des chaînes, les choses NSStringseront toujours disponibles aussi longtemps que nous import Cocoa. Ensuite, nous aurons

var st = "123"                                 // "123"
var ns = (st as NSString)                      // "123"
st == ns                                       // Compile error with Fixit: 'NSString' is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert?
st == ns as String                             // true, content equality
st === ns                                      // compile error: binary operator '===' cannot be applied to operands of type 'String' and 'NSString'
ns === (st as NSString)                        // false, new struct
ns === (st as AnyObject)                       // false, new struct
(st as NSString) === (st as NSString)          // false, new structs, bridging is not "free" (as in "lunch")
NSString(string:st) === NSString(string:st)    // false, new objects
var st1 = NSString(string:st)                  // "123"
var st2 = st1                                  // "123"
st1 === st2                                    // true
var st3 = (st as NSString)                     // "123"
st1 === st3                                    // false
(st as AnyObject) === (st as AnyObject)        // false

Il est toujours déroutant d'avoir deux classes String, mais abandonner la conversion implicite la rendra probablement un peu plus palpable.

Patru
la source
2
Vous ne pouvez pas utiliser d' ===opérateur pour comparer Ints. Pas dans Swift 3.
Jakub Truhlář
Chaque fois que vous dites qu'une "nouvelle structure" est en cours de création, ce qui se passe réellement est qu'un nouvel objet (d'un type classe ) est créé. ===n'a pas de sens pour les structures car ce sont des types de valeur. En particulier, vous devez garder à l'esprit trois types: les types littéraux, tels que 1 ou "foo", qui ne sont pas liés à une variable et n'affectent normalement que la compilation, car vous ne les traitez généralement pas pendant l'exécution; types de struct tels que Intet Stringqui sont ce que vous obtenez lorsque vous affectez un littéral à une variable, et des classes telles que AnyObjectet NSString.
saagarjha
12

Par exemple, si vous créez deux instances d'une classe, par exemple myClass:

var inst1 = myClass()
var inst2 = myClass()

vous pouvez comparer ces instances,

if inst1 === inst2

cité:

que vous utilisez pour tester si deux références d'objet font référence à la même instance d'objet.

Extrait de: Apple Inc. «The Swift Programming Language». iBooks. https://itun.es/sk/jEUH0.l

jm666
la source
11

Dans Swift, nous avons === simbol, ce qui signifie que les deux objets font référence à la même référence et à la même adresse

class SomeClass {
var a: Int;

init(_ a: Int) {
    self.a = a
}

}

var someClass1 = SomeClass(4)
var someClass2 = SomeClass(4)
someClass1 === someClass2 // false
someClass2 = someClass1
someClass1 === someClass2 // true
dara
la source
4

Juste une petite contribution liée à l' Anyobjet.

Je travaillais avec des tests unitaires autour NotificationCenter, qui utilise Anycomme paramètre que je voulais comparer pour l'égalité.

Cependant, Anyne pouvant pas être utilisé dans une opération d'égalité, il a fallu le modifier. En fin de compte, j'ai opté pour l'approche suivante, qui m'a permis d'obtenir l'égalité dans ma situation spécifique, présentée ici avec un exemple simpliste:

func compareTwoAny(a: Any, b: Any) -> Bool {
    return ObjectIdentifier(a as AnyObject) == ObjectIdentifier(b as AnyObject)
}

Cette fonction tire parti d' ObjectIdentifier , qui fournit une adresse unique pour l'objet, me permettant de tester.

Un élément à noter cependant ObjectIdentifierpar Apple sur le lien ci-dessus:

Dans Swift, seules les instances de classe et les métatypes ont des identités uniques. Il n'y a aucune notion d'identité pour les structures, les énumérations, les fonctions ou les tuples.

CodeBender
la source
2

==est utilisé pour vérifier si deux variables sont égales ie 2 == 2. Mais dans le cas où ===cela signifie égalité, c'est-à-dire que si deux instances se référant au même exemple d'objet dans le cas des classes, une référence est créée qui est détenue par de nombreuses autres instances.

alisha chaudhary
la source
1

Swift 4: Un autre exemple utilisant des tests unitaires qui ne fonctionne qu'avec ===

Remarque: le test ci-dessous échoue avec ==, fonctionne avec ===

func test_inputTextFields_Delegate_is_ViewControllerUnderTest() {

        //instantiate viewControllerUnderTest from Main storyboard
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        viewControllerUnderTest = storyboard.instantiateViewController(withIdentifier: "StoryBoardIdentifier") as! ViewControllerUnderTest 
        let _ = viewControllerUnderTest.view

        XCTAssertTrue(viewControllerUnderTest.inputTextField.delegate === viewControllerUnderTest) 
    }

Et la classe étant

class ViewControllerUnderTest: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var inputTextField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        inputTextField.delegate = self
    }
}

L'erreur dans les tests unitaires si vous utilisez == est, Binary operator '==' cannot be applied to operands of type 'UITextFieldDelegate?' and 'ViewControllerUnderTest!'

Naishta
la source