Why Is Casting a Struct to Anyobject Not a Compile Error in Swift

Why is casting a struct to AnyObject not a compile error in swift?

This is a feature to facilitate passing to Cocoa. Any struct can be wrapped into a SwiftValue reference type. If you print type(of: object) you'll see the wrapper.

I don't think there is any contract for "expecting a reference type." More importantly, while "value types" and "reference types" exist in Swift, what really matter is value and reference semantics, which are not expressible in the language. You can create value semantics in reference types and reference semantics in value types, so the Swift type system really isn't of any help in that regard.

The important point here is that you only get this unusual behavior if you explicitly request it by asking for as AnyObject. There are very few reason to write that, and if you are, you had better know exactly what you're doing.

Why can I successfully cast a Swift array struct to AnyObject?

You can cast Array<_> to AnyObject on Apple platforms because of Objective-C bridging.

The Swift compiler and runtime on Apple platforms can convert any Swift type to some sort of reference object. In general, a value type is converted to (wrapped in) an instance of __SwiftValue which is a subclass of NSObject. However, the Swift standard library uses some undocumented language features to customize certain conversions:

  • An Array<_> casts to an NSArray.
  • A Dictionary<_, _> casts to an NSDictionary.
  • A String casts to an NSString.
  • A number or boolean casts to an NSNumber.

Why does SomeStruct() is AnyObject return true?

Ugh, this is one of my gripes with Swift. It's an Objective C interop feature, which although useful, is too implicit/mysterious. This implicit boxing behaviour only happens when Foundation is imported, and only on systems with ObjC support (Apple's platforms).

Some Swift types bridge to specific ObjC counterparts, like NSString, NSNumber, NSArray, NSDictionary, etc. All other swift value types (like structs and tuples) are capable of being wrapped in a private ObjC class called _NSSwiftValue, which makes it possible to hand them off to ObjC APIs.

The most confusing thing is that the object has an ill-defined identity (object address) as far as Objective C is concerned, and if your type doesn't conform to Hashable, then the Hash value of the object is also ill-defined, which can lead to all kinds of hard-to-nail-down bugs.

Failing cast in Swift from Any? to protocol

This is pretty weird bug – it looks like it happens when an instance has been bridged to Obj-C by being boxed in a _SwiftValue and is statically typed as Any(?). That instance then cannot be cast to a given protocol that it conforms to.

According to Joe Groff in the comments of the bug report you filed:

This is an instance of the general "runtime dynamic casting doesn't bridge if necessary to bridge to a protocol" bug. Since sender is seen as _SwiftValue object type, and we're trying to get to a protocol it doesn't conform to, we give up without also trying the bridged type.

A more minimal example would be:

protocol P {}
struct S : P {}

let s = S()

let val : Any = s as AnyObject // bridge to Obj-C as a _SwiftValue.

print(val as? P) // nil

Weirdly enough, first casting to AnyObject and then casting to the protocol appears to work:

print(val as AnyObject as! P) // S()

So it appears that statically typing it as AnyObject makes Swift also check the bridged type for protocol conformance, allowing the cast to succeed. The reasoning for this, as explained in another comment by Joe Groff, is:

The runtime has had a number of bugs where it only attempts certain conversions to one level of depth, but not after performing other conversions (so AnyObject -> bridge -> Protocol might work, but Any -> AnyObject -> bridge -> Protocol doesn't). It ought to work, at any rate.

Why casting function type as AnyObject works

Behind the scenes as AnyObject converts the casted value to an Objective-C compatible one: Int's become NSNumber, array's become NSArray, and so on. Swift-only values get wrapped within opaque SwiftValue instances.

print(type(of: test2)) // __SwiftValue

This is why adding as AnyObject makes your code compile, as the right-hand of the operator is now an object.

How to cast a struct to Anyobject in Swift 2?

A Struct cannot conform to AnyObject. It can only conform to Any

Swift why does as? require AnyObject vs Any

I guess this doesn't really answer the original question. However, it is a workaround that gets the job done. I still don't understand why the solution above doesn't work (especially the fatal error when casting from [AnyObject] to [Any]), but I took a different approach which works great:

extension NSArray {
public func toSwiftArray<Type>() -> [Type] {
var swiftArray = [Type]()

for value in self {
if let valueOfType = value as? Type {
swiftArray.append( valueOfType )
}
}

return swiftArray
}

public func toSwiftArray<Type>() -> ([Type], [Any]) {
var swiftTypeArray = [Type]()
var unknownTypeArray = [Any]()

for value in self {
if let valueOfType = value as? Type {
swiftTypeArray.append( valueOfType )
} else {
unknownTypeArray.append( value )
}
}

return (swiftTypeArray, unknownTypeArray)
}
}

Not sure why I couldn't use .filter to do this, but this is a very straightforward solution to the problem, and it also allows for a version that returns a list of the values that couldn't be converted. This is very handy routine for converting to NSArray with full type safety.

Why do integers not conform to the AnyObject protocol?

There are two kinds of anything types in Swift – Any, which can truly hold anything – a struct, enum or class, and AnyObject, which can only hold classes.

The reason why it seems like AnyObject can hold structs sometimes is that some specific types are implicitly converted to their NSEquivalent for you as needed, to make Objective-C interop less painful.

When you write let ao: AnyObject = Int(1), it isn’t really putting an Int into an AnyObject. Instead, it’s implicitly converting your Int into an NSNumber, which is a class, and then putting that in.

But only some types have this implicit conversion. Int does, but Int32 doesn’t, hence this behaviour:

// fine
let aoInt: AnyObject = Int(1) as NSNumber
// also fine: implicit conversion
let aoImplicitInt: AnyObject = Int(1)
// not fine: error: 'Int32' is not convertible to 'NSNumber'
let aoInt32: AnyObject = Int32(1) as NSNumber
// but the implicit error is less, ahem, explicit
// error: type 'Int32' does not conform to protocol 'AnyObject'
let aoImplicitInt32: AnyObject = Int32(1)

It could be argued there should be more implicit conversions to grease the wheels, but then again, these implicit conversions are the source of much confusion already and the direction in the latest beta is to have fewer of them rather than more.

Testing type for class conformance in Swift

As Martin notes in a comment, any value can be cast to AnyObject in Swift, because Swift will wrap value types in an opaque _SwiftValue class, and the cast will always succeed. There is a way around this, though.

The way to check whether a value is a reference type without this implicit casting is to check whether its type is AnyObject.Type, like so:

func printIsObject(_ value: Any) {
if type(of: value) is AnyObject.Type {
print("Object")
} else {
print("Other")
}
}

class Foo {}
struct Bar {}
enum Quux { case q }

printIsObject(Foo()) // => Object
printIsObject(Bar()) // => Other
printIsObject(Quux.q) // => Other

Note that it's crucial that you check whether the type is AnyObject.Type not is AnyObject. T.self, the object representing the type of the value, is itself an object, so is AnyObject will always succeed. Instead, is AnyObject.Type asks "does this inherit from the metatype of all objects", i.e., "does this object which represents a type inherit from an object that represents all object types?"


Edit: Evidently, I'd forgotten that Swift includes AnyClass as a synonym for AnyObject.Type, so the check can be simplified to be is AnyClass. However, leaving the above as a marginally-expanded explanation for how this works.


If you want this method to also be able to handle Optional values, you're going to have to do a bit of special-casing to add support. Specifically, because Optional<T> is an enum regardless of the type of T, you're going to need to reach in to figure out what T is.

There are a few ways to do this, but because Optional is a generic type, and it's not possible to ask "is this value an Optional<T>?" without knowing what T is up-front, one of the easier and more robust ways to do this is to introduce a protocol which Optional adopts that erases the type of the underlying value while still giving you access to it:

protocol OptionalProto {
var wrappedValue: Any? { get }
}

extension Optional: OptionalProto {
var wrappedValue: Any? {
switch self {
case .none: return nil
case let .some(value):
// Recursively reach in to grab nested optionals as needed.
if let innerOptional = value as? OptionalProto {
return innerOptional.wrappedValue
} else {
return value
}
}
}
}

We can then use this protocol to our advantage in cache:

func cache(id: Int, instance: Any) {
if let opt = instance as? OptionalProto {
if let wrappedValue = opt.wrappedValue {
cache(id: id, instance: wrappedValue)
}

return
}

// In production:
// cache[id] = WeakBox(boxed: instance as AnyObject)

if type(of: instance) is AnyClass {
print("\(type(of: instance)) is AnyClass")
} else {
print("\(type(of: instance)) is something else")
}
}

This approach handles all of the previous cases, but also infinitely-deeply-nested Optionals, and protocol types inside of Optionals:

class Foo {}
struct Bar {}
enum Quux { case q }

cache(id: 1, instance: Foo()) // => Foo is AnyClass
cache(id: 2, instance: Bar()) // => Bar is something else
cache(id: 3, instance: Quux.q) // => Quux is something else

let f: Optional<Foo> = Foo()
cache(id: 4, instance: f) // => Foo is AnyClass

protocol SomeProto {}
extension Foo: SomeProto {}

let p: Optional<SomeProto> = Foo()
cache(id: 5, instance: p) // => Foo is AnyClass

Compiler error when casting Dictionary to AnyObject?

(From my above comment:) The problem is that not all Swift dictionaries can be converted to AnyObject, only those the type [NSObject: AnyObject]. And defining an extension of a generic type that applies only to certain restricted generic types seems not to be possible (there were similar Q&A for array extensions).

You can use a function (as Nate suggested in his answer), or define an extension
for NSDictionary instead.


Update for Swift 2: As of Swift 2, you can define an extension of a generic type that applies only to certain restricted generic types:

extension Dictionary where Key : NSObject, Value : AnyObject {
func toJSONData() -> NSData! {
return try? NSJSONSerialization.dataWithJSONObject(self, options: [])
}
}

Then

let d1 = ["foo" : "bar"].toJSONData()

compiles and works as expected, but

struct Bar {} 
let d2 = [ "foo" : Bar() ].toJSONData()

does not compile because Bar() is not an AnyObject.



Related Topics



Leave a reply



Submit