How are Int and String accepted as AnyHashable?

2019-02-18 07:37发布

How come I can do this?

    var dict = [AnyHashable : Int]()
    dict[NSObject()] = 1
    dict[""] = 2

This implies that NSObject and String are somehow a subtype of AnyHashable but AnyHashable is a struct so, how do they allow this?

标签: swift swift3
3条回答
Animai°情兽
2楼-- · 2019-02-18 08:20

Consider that Optional is an enum, which is also a value type – and yet you're freely able to convert a String to an Optional<String>. The answer is simply that the compiler implicitly performs these conversions for you.

If we look at the SIL emitted for the following code:

let i: AnyHashable = 5

We can see that the compiler inserts a call to _swift_convertToAnyHashable:

  // allocate memory to store i, and get the address.
  alloc_global @main.i : Swift.AnyHashable, loc "main.swift":9:5, scope 1 // id: %2
  %3 = global_addr @main.i : Swift.AnyHashable : $*AnyHashable, loc "main.swift":9:5, scope 1 // user: %9

  // allocate temporary storage for the Int, and intialise it to 5.
  %4 = alloc_stack $Int, loc "main.swift":9:22, scope 1 // users: %7, %10, %9
  %5 = integer_literal $Builtin.Int64, 5, loc "main.swift":9:22, scope 1 // user: %6
  %6 = struct $Int (%5 : $Builtin.Int64), loc "main.swift":9:22, scope 1 // user: %7
  store %6 to %4 : $*Int, loc "main.swift":9:22, scope 1 // id: %7

  // call _swift_convertToAnyHashable, passing in the address of i to store the result, and the address of the temporary storage for the Int.
  // function_ref _swift_convertToAnyHashable
  %8 = function_ref @_swift_convertToAnyHashable : $@convention(thin) <τ_0_0 where τ_0_0 : Hashable> (@in τ_0_0) -> @out AnyHashable, loc "main.swift":9:22, scope 1 // user: %9
  %9 = apply %8<Int>(%3, %4) : $@convention(thin) <τ_0_0 where τ_0_0 : Hashable> (@in τ_0_0) -> @out AnyHashable, loc "main.swift":9:22, scope 1

  // deallocate temporary storage.
  dealloc_stack %4 : $*Int, loc "main.swift":9:22, scope 1 // id: %10

Looking in AnyHashable.swift, we can see the function with the silgen name of _swift_convertToAnyHashable, which simply invokes AnyHashable's initialiser.

@_silgen_name("_swift_convertToAnyHashable")
public // COMPILER_INTRINSIC
func _convertToAnyHashable<H : Hashable>(_ value: H) -> AnyHashable {
  return AnyHashable(value)
}

Therefore the above code is just equivalent to:

let i = AnyHashable(5)

Although it's curious that the standard library also implements an extension for Dictionary (which @OOPer shows), allowing for a dictionary with a Key of type AnyHashable to be subscripted by any _Hashable conforming type (I don't believe there are any types that conform to _Hashable, but not Hashable).

The subscript itself should work fine without a special overload for _Hashable keys. Instead the default subscript (which would take an AnyHashable key) could just be used with the above implicit conversion, as the following example shows:

struct Foo {
    subscript(hashable: AnyHashable) -> Any {
        return hashable.base
    }
}

let f = Foo()
print(f["yo"]) // yo

Edit: In Swift 4, both the aforementioned subscript overload and _Hashable have been removed from the stdlib by this commit with the description:

We have an implicit conversion to AnyHashable, so there's no need to have the special subscript on Dictionary at all.

Which confirms my suspicion.

查看更多
一纸荒年 Trace。
3楼-- · 2019-02-18 08:21

You can find this code, when you cmd-click on [ or ] of dict[NSObject()] = 1 in the Swift editor of Xcode (8.2.1, a little different in 8.3 beta):

extension Dictionary where Key : _AnyHashableProtocol {

    public subscript(key: _Hashable) -> Value?

    public mutating func updateValue<ConcreteKey : Hashable>(_ value: Value, forKey key: ConcreteKey) -> Value?

    public mutating func removeValue<ConcreteKey : Hashable>(forKey key: ConcreteKey) -> Value?
}

_AnyHashableProtocol and _Hashable are hidden types, so you may need to check the Swift source code. Simply:

  • _Hashable is a hidden super-protocol of Hashable, so, Int, String, NSObject or all other Hashable types conform to _Hashable.

  • _AnyHashableProtocol is a hidden protocol, where AnyHashable is the only type which conforms to _AnyHashableProtocol.

So, the extension above is very similar to this code in Swift 3.1.

extension Dictionary where Key == AnyHashable {

    //...
}

You see, when you write such code like this:

    var dict = [AnyHashable : Int]()
    dict[NSObject()] = 1
    dict[""] = 2

You are using the subscript operator defined in the extension.

查看更多
SAY GOODBYE
4楼-- · 2019-02-18 08:25
class SomeClass: NSObject {
   var something: String = "something"
}

var dict = [AnyHashable: Int]()
var object = SomeClass()

dict = ["a": 1, object: 2]

print(dict["a"]) // result: Optional(1)
print(dict[object]) // result: Optional(2)

var object2 = SomeClass()
dict[object2] = 3
print(dict[object2]) // result: Optional(3)
查看更多
登录 后发表回答