Any as AnyObject

I spotted a bug in a 3rd party library where a parameter type should be [String: Any]? instead of [String: AnyObject]?. While converting that dictionary I noticed that doing anyInstance as? AnyObject will raise a warning "Conditional cast from 'Any' to 'AnyObject' always succeeds".

That behavior was unexpected since not every type is a reference type, hence not every any instance should be implicitly an any object instance. I know there are implicit wrapper types behind the scenes for Objective-C interop but still this was very unexpected in my experience.

I quickly opened up playgrounds to test the behavior and found out that we can seamlessly write (Xcode - iOS Playground):

struct S {}
let s1: Any = S()
let s2 = s1 as AnyObject

func foo() {}
let f1: Any = foo
let f2 = f1 as AnyObject
  • Can someone explain this (IMHO odd) behavior please?

  • Could it be that I'm just assuming that behavior of the deferred SE-0083?

Interesting. Directly casting them works as well:

func foo() {}
let f1: () -> () = foo
let f2 = f1 as AnyObject

print(f2 is () -> ()) // true

enum R { case a; case b }
let r = R.a as AnyObject

let x = (() -> ()).self as AnyObject  
let y = Any.self as AnyObject

and they can be successfully force-casted back.

This makes the constraint for class types T: AnyObject invalid.

This is a consequence of SE-0116 – because Objective-C id is bridged to Swift as Any, Any needs to be bridgeable back to id (and therefore AnyObject). So is AnyObject is now always true, and as AnyObject always succeeds.

When trying to bridge an incompatible thing to Obj-C (such as the structure value or function value in your example), Swift will wrap the value in an opaque Obj-C compatible box that can be round tripped across the bridge.

There's more discussion of this behaviour in this Q&A and in the comments of SR-2420.

2 Likes

Looks like that <T: class> constraint isn't that bad of an idea after all :sweat_smile:

1 Like

Noticed this is the current behavior on Linux as well. Do we want to formalize this behavior? Explicit boxing for struct / enum into AnyObject seems to be useful (I am using this behavior upon discovery), but I cannot find any documentation to give me guarantee this is a desirable behavior and won't break in future releases.