odd `==` `!=` behavior with class that inherits `NSObject`


(Zhao Xin) #1

Sample 1: both `==` and `!=` is true.

import Foundation

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

}

let a = Foo(name: "bar")

let b = Foo(name: "bar")

print(a == b) // true

print(a != b) // true

Sample 2: Add above code to a do-block, behavior changes to expect

do {

    class Foo:NSObject {

        let name:String

        init(name:String) {

            self.name = name

        }

        public static func ==(lhs: Foo, rhs: Foo) -> Bool {

            guard type(of:lhs) == type(of:rhs) else { return false }

            return lhs.name == rhs.name

        }

    }

    let a = Foo(name: "bar")

    let b = Foo(name: "bar")

    print(a == b) // false

    print(a != b) // true

}

Sample 3: A little investigation shows that `==` didn't call NSObject's `
func isEqual(_ object: Any?) -> Bool` but `!=` did.

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

    override func isEqual(to object: Any?) -> Bool {

        print("111")

        return super.isEqual(to: object)

    }

    override func isEqual(_ object: Any?) -> Bool {

        print("2222")

        return super.isEqual(object)

    }

}

let a = Foo(name: "bar")

let b = Foo(name: "bar")

print(a == b) // true

print(a != b) // 2222, true

print(!(a == b)) // false

So I am wondering what is the future? Will we keep on using `isEqual(_
object: Any?)` with class that inherits `NSObject`, or we are trying to
drop it?

Xcode 8.3.1 (8E1000a), 3.1 (swiftlang-802.0.51 clang-802.0.41), macOS 10.12.4
(16E195)

Zhaoxin


(Nate Birkholz) #2

Your issue seems to be that you created a custom implementation for the
`==` operator but not one for the `!=` operator. If I add a custom
implementation for `!=` I get the results you expected. Tthe default
implementation of NSObject's `isEqual` is a test for identity, like the
`===` in Swift. So when you ask "is `a` NOT the same object in memory as
`b`?" the object returns "true" because they are indeed not the same object.

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

    public static func !=(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name != rhs.name

    }

}

let a = Foo(name: "bar")

let b = Foo(name: "bar")

print(a == b) // true

print(a != b) // false

···

On Tue, Apr 18, 2017 at 7:33 PM, Zhao Xin via swift-users < swift-users@swift.org> wrote:

Sample 1: both `==` and `!=` is true.

import Foundation

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

}

let a = Foo(name: "bar")

let b = Foo(name: "bar")

print(a == b) // true

print(a != b) // true

Sample 2: Add above code to a do-block, behavior changes to expect

do {

    class Foo:NSObject {

        let name:String

        init(name:String) {

            self.name = name

        }

        public static func ==(lhs: Foo, rhs: Foo) -> Bool {

            guard type(of:lhs) == type(of:rhs) else { return false }

            return lhs.name == rhs.name

        }

    }

    let a = Foo(name: "bar")

    let b = Foo(name: "bar")

    print(a == b) // false

    print(a != b) // true

}

Sample 3: A little investigation shows that `==` didn't call NSObject's `
func isEqual(_ object: Any?) -> Bool` but `!=` did.

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

    override func isEqual(to object: Any?) -> Bool {

        print("111")

        return super.isEqual(to: object)

    }

    override func isEqual(_ object: Any?) -> Bool {

        print("2222")

        return super.isEqual(object)

    }

}

let a = Foo(name: "bar")

let b = Foo(name: "bar")

print(a == b) // true

print(a != b) // 2222, true

print(!(a == b)) // false

So I am wondering what is the future? Will we keep on using `isEqual(_
object: Any?)` with class that inherits `NSObject`, or we are trying to
drop it?

Xcode 8.3.1 (8E1000a), 3.1 (swiftlang-802.0.51 clang-802.0.41), macOS 10.12.4
(16E195)

Zhaoxin

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

--
Nate Birkholz


(Nate Birkholz) #3

I'm not sure exactly how class declarations interact with the scope of a
do{ } statement. I declare a new static function inside your do{ } scope
and it works fine, but something about the mapping of `==` and `!=` to
`isEqual` in NSObject seems to be confused by the scope of the do{ } block.

I'm not sure what the use case is for declaring a class inside a do{ }
block, but it seems like a bug with SNObject operator mapping:

//: Playground - noun: a place where people can play

import UIKit

do{

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func testStatic(_ object: Foo) {

        print(object.name)

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

    public static func !=(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name != rhs.name

    }

    override func isEqual(_ object: Any?) -> Bool {

        print("in isEqual")

        guard let object = object as? Foo else { return false }

        let _ = super.isEqual(object)

        if self.name == object.name {

            return true

        }

        return false

    }

}

    let a = Foo(name: "bar")

    let b = Foo(name: "bar")

    print(a == b) // true

    print(a != b) // false

    Foo.testStatic(a) // bar

}

output is:

in isEqual

true

in isEqual

false

bar

For the record, enclosing just the statements *outside* the class
declaration in a do{ } block works as expected:

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

    public static func !=(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name != rhs.name

    }

    override func isEqual(_ object: Any?) -> Bool {

        print("in isEqual")

        guard let object = object as? Foo else { return false }

        let _ = super.isEqual(object)

        if self.name == object.name {

            return true

        }

        return false

    }

}

do{

    let a = Foo(name: "bar")

    let b = Foo(name: "bar")

    print(a == b) // true

    print(a != b) // false

}

output:

true

false

···

On Tue, Apr 18, 2017 at 8:07 PM, Zhao Xin <owenzx@gmail.com> wrote:

Just put your code in to a do-block. The result will be opposite.

do {

    class Foo:NSObject {

        let name:String

        init(name:String) {

            self.name = name

        }

        public static func ==(lhs: Foo, rhs: Foo) -> Bool {

            guard type(of:lhs) == type(of:rhs) else { return false }

            return lhs.name == rhs.name

        }

        public static func !=(lhs: Foo, rhs: Foo) -> Bool {

            guard type(of:lhs) == type(of:rhs) else { return false }

            return lhs.name != rhs.name

        }

    }

    let a = Foo(name: "bar")

    let b = Foo(name: "bar")

    print(a == b) // true, now false

    print(a != b) // false, now true

}

Zhaoxin

On Wed, Apr 19, 2017 at 11:04 AM, Nate Birkholz <nbirkholz@gmail.com> > wrote:

Your issue seems to be that you created a custom implementation for the
`==` operator but not one for the `!=` operator. If I add a custom
implementation for `!=` I get the results you expected. Tthe default
implementation of NSObject's `isEqual` is a test for identity, like the
`===` in Swift. So when you ask "is `a` NOT the same object in memory as
`b`?" the object returns "true" because they are indeed not the same object.

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

    public static func !=(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name != rhs.name

    }

}

let a = Foo(name: "bar")

let b = Foo(name: "bar")

print(a == b) // true

print(a != b) // false

On Tue, Apr 18, 2017 at 7:33 PM, Zhao Xin via swift-users < >> swift-users@swift.org> wrote:

Sample 1: both `==` and `!=` is true.

import Foundation

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

}

let a = Foo(name: "bar")

let b = Foo(name: "bar")

print(a == b) // true

print(a != b) // true

Sample 2: Add above code to a do-block, behavior changes to expect

do {

    class Foo:NSObject {

        let name:String

        init(name:String) {

            self.name = name

        }

        public static func ==(lhs: Foo, rhs: Foo) -> Bool {

            guard type(of:lhs) == type(of:rhs) else { return false }

            return lhs.name == rhs.name

        }

    }

    let a = Foo(name: "bar")

    let b = Foo(name: "bar")

    print(a == b) // false

    print(a != b) // true

}

Sample 3: A little investigation shows that `==` didn't call NSObject's `
func isEqual(_ object: Any?) -> Bool` but `!=` did.

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

    override func isEqual(to object: Any?) -> Bool {

        print("111")

        return super.isEqual(to: object)

    }

    override func isEqual(_ object: Any?) -> Bool {

        print("2222")

        return super.isEqual(object)

    }

}

let a = Foo(name: "bar")

let b = Foo(name: "bar")

print(a == b) // true

print(a != b) // 2222, true

print(!(a == b)) // false

So I am wondering what is the future? Will we keep on using `isEqual(_
object: Any?)` with class that inherits `NSObject`, or we are trying
to drop it?

Xcode 8.3.1 (8E1000a), 3.1 (swiftlang-802.0.51 clang-802.0.41), macOS 10.12.4
(16E195)

Zhaoxin

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

--
Nate Birkholz

--
Nate Birkholz


(Zhao Xin) #4

Everyone should notice that in the do-block (Sample 2). The result is just
opposite of the code out of it (Sample 1). Or say, out the do-block, Swift
calls `==` directly. In the do-block, Swift just call `isEqual(_)` instead
of `==`.

That is the inconsistency I am confused.

Zhaoxin

···

On Wed, Apr 19, 2017 at 10:33 AM, Zhao Xin <owenzx@gmail.com> wrote:

Sample 1: both `==` and `!=` is true.

import Foundation

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

}

let a = Foo(name: "bar")

let b = Foo(name: "bar")

print(a == b) // true

print(a != b) // true

Sample 2: Add above code to a do-block, behavior changes to expect

do {

    class Foo:NSObject {

        let name:String

        init(name:String) {

            self.name = name

        }

        public static func ==(lhs: Foo, rhs: Foo) -> Bool {

            guard type(of:lhs) == type(of:rhs) else { return false }

            return lhs.name == rhs.name

        }

    }

    let a = Foo(name: "bar")

    let b = Foo(name: "bar")

    print(a == b) // false

    print(a != b) // true

}

Sample 3: A little investigation shows that `==` didn't call NSObject's `
func isEqual(_ object: Any?) -> Bool` but `!=` did.

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

    override func isEqual(to object: Any?) -> Bool {

        print("111")

        return super.isEqual(to: object)

    }

    override func isEqual(_ object: Any?) -> Bool {

        print("2222")

        return super.isEqual(object)

    }

}

let a = Foo(name: "bar")

let b = Foo(name: "bar")

print(a == b) // true

print(a != b) // 2222, true

print(!(a == b)) // false

So I am wondering what is the future? Will we keep on using `isEqual(_
object: Any?)` with class that inherits `NSObject`, or we are trying to
drop it?

Xcode 8.3.1 (8E1000a), 3.1 (swiftlang-802.0.51 clang-802.0.41), macOS 10.12.4
(16E195)

Zhaoxin


(Zhao Xin) #5

Hi Nate,

It my personal habit to use do-block to compare different Objects with the
same name. For example,

do {

    class A {

        // some staff

    }

    let a = A()

    // some other staff

}

do {

    class A {

        func foo() {}

        // some staff

    }

    let a = A()

    // some other staff

}

I don't need to comment/uncomment with do-block when testing my code.

I think for NSObject's `isEqual(_)`, there is a big change and the change
is never mentioned in any docs. As far as I know, `==` had never worked
with NSObject instances in the past. Because only `isEqual(_)` is used.
`==` wouldn't be called at all.

In today's examples, `==` override the behavior `isEqual(_)`. The change is
huge. Because if `==` is never called, someone like me will notice and then
change his own code to override `isEqual(_)`. Now `isEqual(_)` is some how
abandoned if `==` and `!=` is implemented. That never happens before.

// prior Swift, NSObject instances
find code `a==b`, call `a.isEqual(b)`

// now Swift,
find code `a==b`, call ` if has operator `==``, call `a==b`; else call
`a.isEqual(b)`

Zhaoxin

···

On Wed, Apr 19, 2017 at 11:31 AM, Nate Birkholz <nbirkholz@gmail.com> wrote:

I'm not sure exactly how class declarations interact with the scope of a
do{ } statement. I declare a new static function inside your do{ } scope
and it works fine, but something about the mapping of `==` and `!=` to
`isEqual` in NSObject seems to be confused by the scope of the do{ } block.

I'm not sure what the use case is for declaring a class inside a do{ }
block, but it seems like a bug with SNObject operator mapping:

//: Playground - noun: a place where people can play

import UIKit

do{

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func testStatic(_ object: Foo) {

        print(object.name)

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

    public static func !=(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name != rhs.name

    }

    override func isEqual(_ object: Any?) -> Bool {

        print("in isEqual")

        guard let object = object as? Foo else { return false }

        let _ = super.isEqual(object)

        if self.name == object.name {

            return true

        }

        return false

    }

}

    let a = Foo(name: "bar")

    let b = Foo(name: "bar")

    print(a == b) // true

    print(a != b) // false

    Foo.testStatic(a) // bar

}

output is:

in isEqual

true

in isEqual

false

bar

For the record, enclosing just the statements *outside* the class
declaration in a do{ } block works as expected:

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

    public static func !=(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name != rhs.name

    }

    override func isEqual(_ object: Any?) -> Bool {

        print("in isEqual")

        guard let object = object as? Foo else { return false }

        let _ = super.isEqual(object)

        if self.name == object.name {

            return true

        }

        return false

    }

}

do{

    let a = Foo(name: "bar")

    let b = Foo(name: "bar")

    print(a == b) // true

    print(a != b) // false

}

output:

true

false

On Tue, Apr 18, 2017 at 8:07 PM, Zhao Xin <owenzx@gmail.com> wrote:

Just put your code in to a do-block. The result will be opposite.

do {

    class Foo:NSObject {

        let name:String

        init(name:String) {

            self.name = name

        }

        public static func ==(lhs: Foo, rhs: Foo) -> Bool {

            guard type(of:lhs) == type(of:rhs) else { return false }

            return lhs.name == rhs.name

        }

        public static func !=(lhs: Foo, rhs: Foo) -> Bool {

            guard type(of:lhs) == type(of:rhs) else { return false }

            return lhs.name != rhs.name

        }

    }

    let a = Foo(name: "bar")

    let b = Foo(name: "bar")

    print(a == b) // true, now false

    print(a != b) // false, now true

}

Zhaoxin

On Wed, Apr 19, 2017 at 11:04 AM, Nate Birkholz <nbirkholz@gmail.com> >> wrote:

Your issue seems to be that you created a custom implementation for the
`==` operator but not one for the `!=` operator. If I add a custom
implementation for `!=` I get the results you expected. Tthe default
implementation of NSObject's `isEqual` is a test for identity, like the
`===` in Swift. So when you ask "is `a` NOT the same object in memory as
`b`?" the object returns "true" because they are indeed not the same object.

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

    public static func !=(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name != rhs.name

    }

}

let a = Foo(name: "bar")

let b = Foo(name: "bar")

print(a == b) // true

print(a != b) // false

On Tue, Apr 18, 2017 at 7:33 PM, Zhao Xin via swift-users < >>> swift-users@swift.org> wrote:

Sample 1: both `==` and `!=` is true.

import Foundation

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

}

let a = Foo(name: "bar")

let b = Foo(name: "bar")

print(a == b) // true

print(a != b) // true

Sample 2: Add above code to a do-block, behavior changes to expect

do {

    class Foo:NSObject {

        let name:String

        init(name:String) {

            self.name = name

        }

        public static func ==(lhs: Foo, rhs: Foo) -> Bool {

            guard type(of:lhs) == type(of:rhs) else { return false }

            return lhs.name == rhs.name

        }

    }

    let a = Foo(name: "bar")

    let b = Foo(name: "bar")

    print(a == b) // false

    print(a != b) // true

}

Sample 3: A little investigation shows that `==` didn't call NSObject's
`func isEqual(_ object: Any?) -> Bool` but `!=` did.

class Foo:NSObject {

    let name:String

    init(name:String) {

        self.name = name

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        guard type(of:lhs) == type(of:rhs) else { return false }

        return lhs.name == rhs.name

    }

    override func isEqual(to object: Any?) -> Bool {

        print("111")

        return super.isEqual(to: object)

    }

    override func isEqual(_ object: Any?) -> Bool {

        print("2222")

        return super.isEqual(object)

    }

}

let a = Foo(name: "bar")

let b = Foo(name: "bar")

print(a == b) // true

print(a != b) // 2222, true

print(!(a == b)) // false

So I am wondering what is the future? Will we keep on using `isEqual(_
object: Any?)` with class that inherits `NSObject`, or we are trying
to drop it?

Xcode 8.3.1 (8E1000a), 3.1 (swiftlang-802.0.51 clang-802.0.41), macOS 10.12.4
(16E195)

Zhaoxin

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

--
Nate Birkholz

--
Nate Birkholz