Разница между == и ===

299

В Swift, кажется, есть два оператора равенства: двойное равенство ( ==) и тройное равенство ( ===), в чем разница между ними?

Фела Винкельмолен
источник

Ответы:

149

Коротко:

== оператор проверяет, равны ли их значения экземпляра, "equal to"

=== оператор проверяет, указывают ли ссылки на один и тот же экземпляр, "identical to"

Длинный ответ:

Классы являются ссылочными типами, несколько констант и переменных могут ссылаться на один и тот же экземпляр класса за кулисами. Ссылки на классы остаются в стеке времени выполнения (RTS), а их экземпляры остаются в области кучи памяти. Когда вы контролируете равенство с ==ним, значит, если их экземпляры равны друг другу. Это не должен быть один и тот же экземпляр, чтобы быть равным. Для этого вам нужно предоставить критерии равенства для вашего пользовательского класса. По умолчанию пользовательские классы и структуры не получают реализацию операторов эквивалентности по умолчанию, известную как оператор «равно» и оператор ==«не равно» !=. Для этого ваш пользовательский класс должен соответствовать Equatableпротоколу и его static func == (lhs:, rhs:) -> Boolфункциям.

Давайте посмотрим на пример:

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.: Поскольку ssn (номер социального страхования) является уникальным номером, вам не нужно сравнивать, совпадают ли их имена или нет.

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

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

Хотя ссылки person1 и person2 указывают на два разных экземпляра в области кучи, их экземпляры равны, потому что их номера ssn равны. Таким образом, выход будетthe two instance are equal!

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

===Оператор проверяет, указывают ли ссылки на один и тот же экземпляр "identical to". Так как person1 и person2 имеют два разных экземпляра в области кучи, они не идентичны, и результатthe two instance are not identical!

let person3 = person1

P.S: Классы являются ссылочными типами, и ссылка person1 копируется в person3 с помощью этой операции присваивания, поэтому обе ссылки указывают на один и тот же экземпляр в области кучи.

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

Они идентичны и на выходе будут the two instances are identical!

Фатих Аксу
источник
248

!==и ===являются идентичными операторами и используются для определения, имеют ли два объекта одинаковые ссылки.

Swift также предоставляет два идентификатора оператора (=== и! ==), которые вы используете, чтобы проверить, ссылаются ли обе ссылки на один и тот же экземпляр объекта.

Выдержка из: Apple Inc. «Язык программирования Swift». интерактивные книги. https://itun.es/us/jEUH0.l

aglasser
источник
49
Ага. Исходя из ObjC, ==есть isEqual:или определенная классом семантическая эквивалентность. ===в Swift есть ==в (Obj) C - равенство указателей или идентичность объекта.
Рикстер
Значения @rickster Dont 'также имеют место в памяти? Я в конце концов они где-то в памяти. Разве вы не можете сравнить их? Или это место их памяти не имеет какого-либо значимого значения?
Мед
2
Есть как минимум два способа подумать о том, как язык определяет типы значений по сравнению с памятью. Во-первых, каждая привязка ( varили let) имени к значению является уникальной копией, поэтому создавать указатели бессмысленно, поскольку значение, на которое вы указали, является значением, отличным от того, которое вы создали в первый раз. Другое заключается в том, что определение семантики значений в Swift абстрагирует хранилище - компилятор может свободно оптимизировать, вплоть до того, что он никогда не сохранит ваше значение в ячейке памяти, доступной за строкой, в которой он используется (регистр, кодирование инструкций и т. Д.).
Рикстер
62

В обоих Objective-C и Свифта, то ==и !=тест операторы для значения равенства числовых значений (например, NSInteger, NSUInteger, int, в Objective-C и Int, UIntи т.д. в Swift). Для объектов (NSObject / NSNumber и подклассов в Objective-C и ссылочных типов в Swift) ==и !=проверьте, что объекты / ссылочные типы - это одно и то же, то есть одно и то же хеш-значение, или не одно и то же, соответственно ,

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

Операторы равенства тождества Свифта ===и !==, проверяющие ссылочное равенство - и, таким образом, вероятно, должны называться операторами ссылочного равенства ИМО.

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

Стоит также отметить, что пользовательские ссылочные типы в Swift (которые не подклассируют класс, который соответствует Equatable) не реализуют автоматически операторы равенства , но операторы равенства идентичности по- прежнему применяются. Также, путем реализации ==, !=автоматически реализуется.

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

Эти операторы равенства не реализованы для других типов, таких как структуры на любом языке. Однако пользовательские операторы могут быть созданы в Swift, что, например, позволит вам создать оператор для проверки равенства 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
Скотт Гарднер
источник
3
Извините, но в Obj-C оператор == НЕ сравнивается для EQUALITY, а скорее - как C - сравнивает ссылки на указатели (идентификатор объекта).
Мотти Шнеор
==не проверяет на NSNumberравенство в Objective-C. NSNumberэто NSObjectтак тестирует на личность. Причина, по которой он работает SOMETIMES, заключается в том, что теги-указатели / кэшированные литералы объектов. Это не удастся для достаточно больших чисел и на 32-битных устройствах при сравнении не-литералов.
Accatyc
45

Свифт 3 и выше

===(или !==)

  • Проверяет, идентичны ли значения (оба указывают на один и тот же адрес памяти) .
  • Сравнение ссылочных типов .
  • Как ==в Obj-C (равенство указателей).

==(или !=)

  • Проверяет , имеют ли значения то же самое .
  • Сравнение типов значений .
  • Как по умолчанию isEqual:в поведении Obj-C.

Здесь я сравниваю три экземпляра (класс является ссылочным типом)

class Person {}

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

person === person2 // true
person === person3 // false
Якуб Трухларж
источник
Вы также можете переопределить isEqual:в Swift:override func isEqual(_ object: Any?) -> Bool {}
Томас Эллиот
37

У Swifts есть тонкости, ===которые выходят за рамки простой арифметики указателей. В то время как в Objective-C вы могли сравнивать любые два указателя (то есть NSObject *) с ==этим больше не верно в Swift, так как типы играют гораздо большую роль во время компиляции.

Детская площадка даст вам

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

Со строками нам придется привыкнуть к этому:

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

но тогда вы также можете повеселиться следующим образом:

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

Я уверен, что вы можете подумать о гораздо более забавных случаях :-)

Обновление для Swift 3 (согласно предложению Якуба Трухларжа)

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)

Это выглядит немного более согласуется с Type 'Int' does not conform to protocol 'AnyObject', однако мы тогда получим

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

но явное преобразование проясняет, что может быть что-то происходит. На стороне String все NSStringеще будет доступно, пока мы import Cocoa. Тогда у нас будет

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

Это по - прежнему запутанным иметь два класса String, но сбросив неявное преобразование, вероятно , сделать его немного более ощутимым.

Патру
источник
2
Вы не можете использовать ===оператор для сравнения Ints. Не в Swift 3.
Якуб Трухларж
Всякий раз, когда вы говорите, что создается «новая структура», на самом деле создается новый объект (типа класса ). ===не имеет смысла для структур, поскольку они являются типами значений. В частности, нужно помнить о трех типах: литеральные типы, такие как 1 или «foo», которые не привязаны к переменной и обычно влияют только на компиляцию, так как вы обычно не работаете с ними во время выполнения; Типы struct, такие как Intи Stringкоторые вы получаете при назначении литерала переменной, и классы, такие как AnyObjectи NSString.
Саагарджа
12

Например, если вы создаете два экземпляра класса, например myClass:

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

Вы можете сравнить эти случаи,

if inst1 === inst2

цитируется:

который вы используете, чтобы проверить, ссылаются ли обе ссылки на один и тот же экземпляр объекта.

Выдержка из: Apple Inc. «Язык программирования Swift». интерактивные книги. https://itun.es/sk/jEUH0.l

jm666
источник
11

В Swift у нас есть === simbol, что означает, что оба объекта ссылаются на один и тот же ссылочный адрес

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
Дара
источник
4

Просто незначительный вклад, связанный с Anyобъектом.

Я работал с модульными тестами NotificationCenter, которые используют Anyв качестве параметра, который я хотел сравнить на равенство.

Однако, поскольку Anyнельзя использовать в операции равенства, необходимо было ее изменить. В конечном итоге я остановился на следующем подходе, который позволил мне добиться равенства в моей конкретной ситуации, показанной здесь на упрощенном примере:

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

Эта функция использует ObjectIdentifier , который предоставляет уникальный адрес для объекта, что позволяет мне тестировать.

Один пункт, чтобы отметить, хотя о ObjectIdentifierв Apple по ссылке выше:

В Swift только экземпляры классов и метатипы имеют уникальные идентификаторы. Не существует понятия идентичности для структур, перечислений, функций или кортежей.

CodeBender
источник
2

==используется для проверки , если две переменные равны т 2 == 2. Но в случае ===этого означает равенство, т.е. если два экземпляра ссылаются на один и тот же пример объекта в случае классов, создается ссылка, которая содержится во многих других экземплярах.

Алиша Чаудхари
источник
1

Swift 4: еще один пример использования модульных тестов, который работает только с ===

Примечание: Тест ниже не проходит с ==, работает с ===

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

И класс существо

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

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

Ошибка в модульных тестах, если вы используете ==, Binary operator '==' cannot be applied to operands of type 'UITextFieldDelegate?' and 'ViewControllerUnderTest!'

Naishta
источник