In the old age of Objective-C, we often use methods like -isEqualToString: to check two NSString objects are the same if we already know thier types. In Swift, there is no such methods like -isEqualToString: or -isEqual: for the standard String type. They are things only in Objective-C. The equality checking can be done by simply using == operator:

let str1 = "Hello world"
let str2 = "Hello world"
let str3 = "Goodbye"

str1 == str2  // true
str1 == str3  // false

There is a huge gap between the behavior in Swift and Objective-C. In Objective-C, == means checking the memory addresses equal or not. Most of time, this is not what we want. Instead of the memory address, we are more interesting about whether the contents are the same or not. We usually override the -isEqual: in Objective-C. Or take a step further by implementing a class-specified version of this method like -isEqualToSomeClass: (think about -isEqualToString:) to check the content of two objects are the same. If we didn't override -isEqual: in any subclasses, the original one in NSObject would be called, and do a == check instead.

In Swift, things are different. == is an operator declaration for Equatable protocol:

protocol Equatable {
    func ==(lhs: Self, rhs: Self) -> Bool
}

To conform to this protocol, we need to implement the == operator of the type. When we believe the two input objects should be the same ones, we should return true for it. Once the type conforms to Equatable, we can use == and != to check the equality (Only == is needed to implement. != will be the opposite of == automatically). Consider a to-do list app with an array of to-do items taken from the database, which are indexed by a uuid. In practice, we often use the uuid to check whether two entries are the same one. Making this TodoItem to conform Equatable protocol would be easy:

class TodoItem {
    let uuid: String
    var title: String

    init(uuid: String, title: String) {
        self.uuid = uuid
        self.title = title
    }
}

extension TodoItem: Equatable {

}

func ==(lhs: TodoItem, rhs: TodoItem) -> Bool {
    return lhs.uuid == rhs.uuid
}

Notice that we didn't put the implementation of == in the Equatable extension scope of TodoItem. Since operators should be accessible in a global scope. For more information about it, please visit the Operator tip for more.

All regular types in Swift have already implemented ==. For NSObject and its subclasses, if we do not override ==, it will turn to call the -isEqual: method when we use ==. Thanks to it, if this NSObject subclass already overrides -isEqual:, the using of == will not cause any difference from a pure Swift type. That means we have two ways to implement equality checking for NSObject subclass: override == or overwrite -isEqual:. If you only use this class in Swift, the two approaches are equivalent. However, if you need to use this class in an Objective-C context, you have to remember that there is no operator override in Objective-C. You can only choose to use the -isEqual:.

For the comparison for memory address, Swift supplies another operator for it: ===. There is only one override for === in Swift:

func ===(lhs: AnyObject?, rhs: AnyObject?) -> Bool

It will check two AnyObjects are referring to the same memory or not.

A closely related topic of equality is Hash. Since hash is another more complex issue, I put it in a standalone tip. But in practice, if you need to override == or overwrite -isEqual:, I suggest you look at the Hash topic as well. We really need to supply a good hash for an equitable item to avoid some strange behavior or poor performance when using it as a key in a dictionary.