Różnica między == a ===

300

W trybie szybkim wydaje się, że istnieją dwa operatory równości: podwójny równa się ( ==) i potrójny równa się ( ===), jaka jest różnica między tymi dwoma?

Fela Winkelmolen
źródło

Odpowiedzi:

149

W skrócie:

== operator sprawdza, czy wartości ich instancji są równe, "equal to"

=== operator sprawdza, czy odniesienia wskazują tę samą instancję, "identical to"

Długa odpowiedź:

Klasy są typami referencyjnymi, wiele stałych i zmiennych może odnosić się do tego samego pojedynczego wystąpienia klasy za scenami. Odwołania do klas pozostają w stosie czasu wykonywania (RTS), a ich instancje pozostają w obszarze sterty pamięci. Kiedy kontrolujesz równość ==, oznacza to, czy ich instancje są sobie równe. Nie musi to być ta sama instancja, aby być równym. W tym celu musisz podać kryteria równości dla swojej klasy niestandardowej. Domyślnie niestandardowe klasy i struktury nie otrzymują domyślnej implementacji operatorów równoważności, znanych jako operator „równy” ==i operator „nie równy” !=. Aby to zrobić, twoja klasa niestandardowa musi być zgodna z Equatableprotokołem i jego static func == (lhs:, rhs:) -> Boolfunkcją

Spójrzmy na przykład:

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.: Ponieważ ssn (numer ubezpieczenia społecznego) jest unikalnym numerem, nie trzeba porównywać, czy ich nazwa jest równa czy nie.

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

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

Chociaż odniesienia person1 i person2 wskazują dwa różne wystąpienia w obszarze sterty, ich wystąpienia są równe, ponieważ ich liczby ssn są równe. Więc wynik będziethe two instance are equal!

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

===Kontrola operatora w przypadku odniesienia wskazują ten sam przykład, "identical to". Ponieważ person1 i person2 mają dwie różne instancje w obszarze sterty, nie są one identyczne i dane wyjściowethe two instance are not identical!

let person3 = person1

P.S: Klasy są typami referencji, a referencja osoby1 jest kopiowana do osoby3 za pomocą tej operacji przypisania, dlatego oba odniesienia wskazują tę samą instancję w obszarze Sterty.

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

Są identyczne, a wynik będzie the two instances are identical!

Fatih Aksu
źródło
248

!==i ===są operatorami tożsamości i służą do ustalenia, czy dwa obiekty mają to samo odniesienie.

Swift udostępnia również dwa operatory tożsamości (=== i! ==), których używasz do testowania, czy dwa odwołania do obiektów odnoszą się do tej samej instancji obiektu.

Fragment: Apple Inc. „Swift Programming Language”. iBooks. https://itun.es/us/jEUH0.l

aglasser
źródło
49
Tak. Pochodzący z ObjC, ==jest isEqual:lub semantyczna równoważność zdefiniowana przez klasę. ===w Swift jest ==w (Obj) C - równość wskaźnika lub tożsamość obiektu.
rickster
Wartości @Rickster Dont mają także lokalizację w pamięci? W końcu jestem gdzieś w pamięci. Czy nie możesz ich kiedykolwiek porównać? A może ich lokalizacja w pamięci nie ma żadnej znaczącej wartości?
Honey
2
Istnieją co najmniej dwa sposoby myślenia o tym, jak język definiuje typy wartości a pamięć. Jednym z nich jest to, że każde wiązanie ( varlub let) nazwy z wartością jest unikalną kopią - więc tworzenie wskaźników nie ma sensu, ponieważ wartość, na którą utworzyłeś wskaźnik, jest inną wartością niż ta, którą stworzyłeś po raz pierwszy. Innym jest to, że definicja semantyki wartości Swifta odciąga pamięć - kompilator może dowolnie optymalizować, aż do przechowywania wartości w miejscu dostępnym poza linią, w której jest używana (rejestr, kodowanie instrukcji itp.).
rickster,
62

Zarówno cel C i SWIFT, ==a !=testy operatorów równości wartości dla wartości liczbowych (na przykład NSInteger, NSUInteger, intw celu C i Int, UIntitp Swift). W przypadku obiektów (NSObject / NSNumber i podklasy w Objective-C i typy referencyjne w Swift) ==i !=przetestuj, czy obiekty / typy referencyjne są tą samą identyczną rzeczą - tj. Taką samą wartością skrótu - lub nie są odpowiednio identyczne .

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

Operatory równości tożsamości Swifta ===i !==sprawdzenie równości referencyjnej - a zatem powinny być prawdopodobnie nazywane operatorami równości referencyjnej IMO.

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

Warto również zauważyć, że niestandardowe typy referencyjne w Swift (które nie podklasują klasy zgodnej z Equatable) nie implementują automatycznie operatorów równości, ale operatory równości tożsamości nadal mają zastosowanie. Ponadto, poprzez wdrożenie ==, !=jest automatycznie realizowane.

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

Te operatory równości nie są implementowane dla innych typów, takich jak struktury w obu językach. Jednak w Swift można tworzyć niestandardowe operatory, które umożliwiłyby na przykład utworzenie operatora sprawdzającego równość 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
źródło
3
Przykro nam, ale w Obj-C operator == NIE porównuje RÓWNOŚCI, ale raczej - podobnie jak C - porównuje odniesienia do wskaźników (Tożsamość obiektu).
Motti Shneor
==nie sprawdza NSNumberrówności w celu C. NSNumberjest NSObjectwięc testuje tożsamość. Powodem, dla którego SOMETIMES działa, są otagowane wskaźniki / literały obiektów buforowanych. Nie powiedzie się w przypadku wystarczająco dużych liczb i na urządzeniach 32-bitowych podczas porównywania literałów.
Accatyyc
45

W szybkim 3 i wyższym

===(lub !==)

  • Sprawdza, czy wartości są identyczne (oba wskazują ten sam adres pamięci) .
  • Porównywanie typów referencyjnych .
  • Jak ==w Obj-C (równość wskaźnika).

==(lub !=)

  • Sprawdza, czy wartości są takie same .
  • Porównywanie typów wartości .
  • Podobnie jak domyślne isEqual:zachowanie Obj-C.

Tutaj porównuję trzy instancje (klasa jest typem referencyjnym)

class Person {}

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

person === person2 // true
person === person3 // false
Jakub Truhlář
źródło
Możesz także zastąpić isEqual:w Swift:override func isEqual(_ object: Any?) -> Bool {}
Thomas Elliot
37

W jerzykach są subtelności, ===które wykraczają poza zwykłą arytmetykę wskaźników. Będąc w Objective-C mogłeś porównać dowolne dwa wskaźniki (tj. NSObject *) Z== tym nie jest już prawdą w Swift, ponieważ typy odgrywają znacznie większą rolę podczas kompilacji.

Plac zabaw da ci

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))

Z ciągami będziemy musieli się do tego przyzwyczaić:

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

ale możesz także bawić się w następujący sposób:

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

Jestem pewien, że możesz wymyślić o wiele zabawniejszych przypadków :-)

Aktualizacja dla Swift 3 (zgodnie z sugestią Jakuba Truhlářa)

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)

Wygląda to nieco bardziej spójnie Type 'Int' does not conform to protocol 'AnyObject', ale potem się pojawia

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

ale wyraźna konwersja wyjaśnia, że ​​coś może się dziać. Po stronie String rzeczy NSStringbędą dostępne tak długo, jak my import Cocoa. Wtedy będziemy mieli

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

Wciąż mylące są dwie klasy String, ale porzucenie niejawnej konwersji prawdopodobnie sprawi, że będzie ona nieco bardziej namacalna.

Patru
źródło
2
Nie można użyć ===operatora do porównania Ints. Nie w Swift 3.
Jakub Truhlář,
Ilekroć mówisz, że tworzona jest „nowa struktura”, tak naprawdę dzieje się nowy obiekt ( typu klasy ). ===nie ma znaczenia dla struktur, ponieważ są to typy wartości. W szczególności należy pamiętać o trzech typach: typach literalnych, takich jak 1 lub „foo”, które nie są powiązane ze zmienną i zwykle wpływają tylko na kompilację, ponieważ generalnie nie radzisz sobie z nimi podczas działania; typy struct takie jak Inti, Stringktóre otrzymujesz po przypisaniu literału zmiennej, oraz klasy takie jak AnyObjecti NSString.
saagarjha
12

Na przykład, jeśli utworzysz dwa wystąpienia klasy, np . myClass:

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

możesz porównać te wystąpienia,

if inst1 === inst2

cytowane:

którego używasz do sprawdzenia, czy oba odwołania do obiektów odnoszą się do tej samej instancji obiektu.

Fragment: Apple Inc. „Swift Programming Language”. iBooks. https://itun.es/sk/jEUH0.l

jm666
źródło
11

W Swift mamy simbol ===, co oznacza, że ​​oba obiekty odnoszą się do tego samego odwołania tego samego adresu

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
źródło
4

Tylko niewielki wkład związany z Anyobiektem.

Pracowałem przy testach jednostkowych NotificationCenter, które wykorzystująAny jako parametr, który chciałem porównać dla równości.

Ponieważ jednak Anynie można go użyć w operacji równości, konieczna była jego zmiana. Ostatecznie zdecydowałem się na następujące podejście, które pozwoliło mi uzyskać równość w mojej konkretnej sytuacji, pokazanej tutaj na uproszczonym przykładzie:

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

Ta funkcja korzysta z ObjectIdentifier , który zapewnia unikalny adres dla obiektu, umożliwiając mi testowanie.

Jedną rzecz wartą uwagi na temat ObjectIdentifierna Apple pod powyższym linkiem:

W Swift tylko instancje klas i metatify mają unikalne tożsamości. Nie ma pojęcia tożsamości dla struktur, wyliczeń, funkcji lub krotek.

CodeBender
źródło
2

==służy do sprawdzenia, czy dwie zmienne są równe, tj 2 == 2. Ale w przypadku ===równości oznacza to, że w przypadku dwóch instancji odnoszących się do tego samego obiektu przykład w przypadku klas tworzone jest odwołanie, które jest przechowywane przez wiele innych instancji.

alisha chaudhary
źródło
1

Swift 4: Kolejny przykład użycia testów jednostkowych, który działa tylko z ===

Uwaga: test poniżej kończy się niepowodzeniem z ==, działa z ===

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) 
    }

A klasa jest

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

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

Błąd w testach jednostkowych, jeśli użyjesz ==, Binary operator '==' cannot be applied to operands of type 'UITextFieldDelegate?' and 'ViewControllerUnderTest!'

Naishta
źródło