Why can any value be cast to `AnyObject`?

I was surprised to find that the following code produces a warning:

func foo<T>(_ t: T) -> ObjectIdentifier? {
    if let r = t as? AnyObject {
        return ObjectIdentifier(r)
    } else {
        return nil
    }
}

Warning: Conditional cast from 'T' to 'AnyObject' always succeeds

I explored a bit more by running this function with integers, and it seems that each unique value corresponds to a distinct object identifier:

let a = 7
print(fooo(a)!) // ObjectIdentifier(0x89815300d8f91468)
print(fooo(a)!) // ObjectIdentifier(0x89815300d8f91468)
print(fooo(7)!) // ObjectIdentifier(0x89815300d8f91468)
print(fooo(8)!) // ObjectIdentifier(0x89815300d8f913e8)
var b = 8
print(fooo(b)!) // ObjectIdentifier(0x89815300d8f913e8)
b += 1
print(fooo(b)!) // ObjectIdentifier(0x89815300d8f913e8)

Why do we allow casting any value to AnyObject when it is not the case that every value "conforms" to it?

I put "conforms" in quotes because I'm aware that AnyObject is not a normal protocol, rather a "layout constraint" or something like that.

I'm assuming that this special nature of AnyObject is related to why the conditional cast always succeeds, but I would love to read about the technical details in more depth.

2 Likes

Casting to AnyObject succeeds by design; the point of AnyObject is to represent a reference to some reference type. If you already have a reference type (i.e. a class instance), then that already "conforms" to AnyObject anyway, but if you have a value type, then casting to AnyObject is really an instruction to the Swift compiler to box it in a reference type for you.

You can see this in the REPL; on Linux it looks like this:

  1> struct Foo {}
  2> Foo() as AnyObject
$R0: __SwiftValue = {
  value = {}
}

while on Darwin (where we have ObjC interop), it instead looks like

  1> struct Foo {}
  2> Foo() as AnyObject
$R0: __SwiftValue = {
  baseNSObject@0 = {
    isa = __SwiftValue
  }
}

I see. Off the top of your head, do you know of examples where this ability to "box up a value in a reference type" is useful?

1 Like

It's probably most useful when passing Swift things through ObjC code; for instance, various ObjC APIs provide the ability to pass through an arbitrary id to some callback, and in that case you can use as AnyObject to sneak a Swift struct through without having to change it into an @objc class.

1 Like

It's an interesting question.

I know this can help for interoperability with Objective-C:

let int = 1
let object = int as AnyObject
object is NSNumber // true

enum E {
    case foo
}
let object = E.foo as AnyObject
object is NSObject // true

This makes it possible to put any Swift-only type into an Objective-C container:

let array = NSArray(object: E.foo)
let output = array.firstObject as! E
print(output) // foo

(I don't expect recent code to use such a feature).

In the context of Swift environment without the Objective-C runtime, I don't know what is the purpose of this feature.

3 Likes

I was going to say that you can't even fix this with overloading, but apparently this works even though slightly more complicated examples don't.

func foo<T>(_ t: T) -> ObjectIdentifier? {
  nil
}

func foo<T: AnyObject>(_ t: T) -> ObjectIdentifier? {
  ObjectIdentifier(t)
}

final class Object { }

print(foo(42) as Any)
print(foo(Object()) as Any)
What doesn't (and has never) worked.
func with<T>(_ value: consuming T, update: (inout T) throws -> ()) rethrows -> T {
  try update(&value)
  return value
}

func with<T: AnyObject>(_ value: T, update: (T) throws -> ()) rethrows -> T {
  try update(value)
  return value
}

final class Object { var name = "" }

print(with(42) { $0 = $0 * 3 / 2 })
print(with(Object()) { $0.name = "Puddentain" })
1 Like