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
al45tair
(Alastair Houghton)
2
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
al45tair
(Alastair Houghton)
4
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