iosswift

Why is `ObjectIdentifier` of 2 different `__SwiftValue` the same in Swift?


I have this Swift struct:

struct Foo {
  let i: Int
}
let f1 = Foo(i: 1)
let f2 = Foo(i: 2)

Then in LLDB, I cast them to __SwiftValue and print out:

p (f1 as AnyObject)

(__SwiftValue) 0x00000001676332d0 {
  baseNSObject@0 = {
    isa = __SwiftValue
  }
}

p (f2 as AnyObject)


(__SwiftValue) 0x0000000167633300 {
  baseNSObject@0 = {
    isa = __SwiftValue
  }
}

As you can tell, the address of the 2 are different, which makes sense, they are different data in separate memory locations anyways.

However, if I take ObjectIdentifier, they are equal:

p ObjectIdentifier(f1 as AnyObject)
(ObjectIdentifier) ObjectIdentifier(0x00000001676332d0) {
  _value = 0x00000001676332d0
}

p ObjectIdentifier(f2 as AnyObject)

(ObjectIdentifier) ObjectIdentifier(0x00000001676332d0) {
  _value = 0x00000001676332d0
}

This weird behavior basically says "equal ObjectIdentifier" doesn't mean "equal address" (===):

p (f1 as AnyObject === f2 as AnyObject)
(Bool)  (_value = 0)

p (ObjectIdentifier(f1 as AnyObject) == ObjectIdentifier(f2 as AnyObject))
(Bool)  (_value = 1)

Why is it so? My understanding is that __SwiftValue is a subclass of NSObject, so they should have different ObjectIdentifiers. This is weird.

Update:

I added example code outside of LLDB:

      let f1 = Foo(i: 1)
      let f2 = Foo(i: 2)
      
      let b1 = (f1 as AnyObject) === (f2 as AnyObject)
      let b2 = ObjectIdentifier(f1 as AnyObject) == ObjectIdentifier(f2 as AnyObject)
      

And got b1 = false and b2 = true in the debugger:

enter image description here


Solution

  • The documentation says:

    This unique identifier is only valid for comparisons during the lifetime of the instance.

    In the expression:

    ObjectIdentifier(f1 as AnyObject) == ObjectIdentifier(f2 as AnyObject)
    

    After the left hand side of == produces an ObjectIdentifier, the Obj-C object created by f1 as AnyObject is not alive anymore. The ObjectIdentifier is no longer valid for comparisons, so the result of the == comparison is meaningless.

    The deinitialisation of f1 as AnyObject can be seen more clearly if you replace it with your own class:

    class InitDeinitPrinter {
        init() {
            print("init")
        }
        
        deinit {
            print("deinit")
        }
    }
    
    ObjectIdentifier(InitDeinitPrinter()) == ObjectIdentifier(InitDeinitPrinter())
    // this prints:
    // init
    // deinit
    // init
    // deinit
    // demonstrating that one class instance is deinitialised before the other instance is initialised
    

    If you first store f1 as AnyObject and f2 as AnyObject into local variables, you keep them alive, and you can legitimately compare their object identifiers.

    let f1 = Foo(i: 1)
    let f2 = Foo(i: 2)
    
    let boxed1 = f1 as AnyObject
    let boxed2 = f2 as AnyObject
    let b1 = boxed1 === boxed2
    let b2 = ObjectIdentifier(boxed1) == ObjectIdentifier(boxed2)
    print(b1, b2) // false, false
    

    See also: https://github.com/swiftlang/swift/issues/56002