I’ve been trying to figure out the rationale for why the code below behaves the 
way it does for some time:

import Foundation

class C: NSObject {
        dynamic var foo: Int { return 5 }
        dynamic func bar() -> Int { return 6 }
}

struct S {}

let c = C()
let s = S()

print(c is AnyObject) // warning: 'is' test is always true
print(s is AnyObject) // warning: 'is' test is always true (?!)

print(c as AnyObject) // <Project.C: 0x0123456789012345>
print(s as AnyObject) // Project.S()

print(c as? AnyObject) // warning: conditional cast from 'C' to 'AnyObject' 
always succeeds
print(s as? AnyObject) // warning: conditional cast from 'S' to 'AnyObject' 
always succeeds

print(c as AnyObject? as Any) // Optional(<Project.C: 0x0123456789012345>)
print(s as AnyObject? as Any) // Optional(Project.S())

print((c as AnyObject?)?.foo as Any) // Optional(5)
print((s as AnyObject?)?.foo as Any) // nil
print((c as AnyObject?)?.bar() as Any) // Optional(6)
print((s as AnyObject?)?.bar() as Any) // crash! -[_SwiftValue bar]: 
unrecognized selector sent to instance 0x5432109876543210

So what we have is:

1. Any type you have will always claim it’s a class type, every time, even if 
it’s actually a non-bridgeable value type.

2. Conditional casting will also succeed, even if you use it on a 
non-bridgeable value type.

3. Non-conditional casting works too, despite that the underlying type might be 
a non-bridgeable value type.

4. Bridging to an optional will *also* always give you a value, even if what’s 
underneath is a non-bridgeable value type.

5. Trying to call a property on an optional from #4 will, surprisingly, work as 
you’d expect. The class type that implements the property returns the property, 
the value type returns nil.

6. Trying to call a method on an optional from #4 will crash.

This raises a few questions:

1. Why in the blazes is it implemented like this? Why not only allow 
conditional casting to AnyObject, which would only succeed if the type either 
actually was an object or could actually be bridged to an object? Why make the 
cast guaranteed, even when in actuality it’s not?

2. Why is there no obvious way to figure out whether something can actually be 
an object? The already kind of non-obvious “type(of: s) is AnyObject.Type” 
trick works to tell you if the thing is already a class, but not if something 
is bridgeable to a class; using it on a string, for example, returns false. And 
trying something like “type(of: s as AnyObject) is AnyObject.Type” returns true 
(?!), so that doesn’t work to detect bridgeable things.

3. Is the behavior in #5 guaranteed? i.e., is this safe:

@IBAction func foo(_ sender: Any) {
    let tag = (sender as AnyObject?)?.tag ?? 0 // kinda ugly

    ...
}

or should we all be doing something like this:

@IBAction func foo(_ sender: Any) {
    let tag = type(of: sender) is AnyObject.Type ? (sender as AnyObject?)?.tag 
?? 0 : 0 // holy cow, ugly

    …
}

(and yes, I did file a radar asking for a “contains tag” protocol so we 
wouldn’t have to mess with this stuff here. rdar://29623107 <rdar://29623107>)

Is there a reason it works this way? I’m tempted to write up a proposal to get 
rid of “as AnyObject” and just have people do an “as? AnyObject” which will 
work if it can be done and return nil otherwise, but I’m curious as to what the 
reasoning behind the current behavior is.

Charles

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to