Is it just for ObjC inter-op? For pure Swift code, it's still needed?
For example, you can create a dictionary with heterogeneous key types
var dict = [AnyHashable: String]()
dict[1] = "integer"
dict["2"] = "string"
print(dict) // [AnyHashable(1): "integer", AnyHashable("2"): "string"]
It's not precisely as simple as what you said, but in my experience, it's close. See my comment here for my most common use case.
/// A type whose `Hashable` conformance could be auto-synthesized,
/// but either the API provider forgot, or more likely,
/// the API is written in Objective-C, and hasn't been modernized.
public protocol HashableSynthesizable: Hashable { }
public extension HashableSynthesizable {
static func == (hashable0: Self, hashable1: Self) -> Bool {
zip(hashable0.hashables, hashable1.hashables).allSatisfy(==)
}
func hash(into hasher: inout Hasher) {
hashables.forEach { hasher.combine($0) }
}
}
private extension HashableSynthesizable {
var hashables: [AnyHashable] {
Mirror(reflecting: self).children
.compactMap { $0.value as? AnyHashable }
}
}
Are there any API's that return AnyHashable
? Maybe something in ObjC like NSxxx use AnyHashable
and return AnyHashable
?
Untyped NSDictionary *
is bridged to Swift as [AnyHashable: Any]
Untyped NSSet *
is bridged to Swift as Set<AnyHashable>
// same either way:
let set = NSSet(objects: 1, 2, 3, 4, 5) as Set
//let set = NSSet(array: [1, 2, 3, 4, 5]) as Set
print(type(of: set)) // Set<NSObject>
let first = set.first! // should this return AnyHashable?
print(type(of: first), first) // __NSCFNumber 3
Am I using this wrong? Getting Set<NSObject>
and element is __NSCFNumber
How to get Set<AnyHashable>
out of NSSet
?
AnyHashable
is useful when you want an enum
case to carry an arbitrary Hashable
. For example, SwiftUI's enum CoordinateSpace
uses AnyHashable
:
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public enum CoordinateSpace {
case global
case local
case named(AnyHashable)
}
If an individual case of an enum
could be generic, then the named
case could be written without AnyHashable
:
case named<H: Hashable>(H)
But that's not allowed. They could have made CoordinateSpace
itself generic:
public enum CoordinateSpace<H: Hashable> {
case global
case local
case named(H)
}
But this has other problems. For one, you have to pick some arbitrary Hashable
type when you want the global
or local
space. That is, CoordinateSpace.local
is invalid; you have to say something like CoordinateSpace<Int>.local
. It also embeds the choice of Hashable
into the modified View
's type, which means it's part of the modified View
's identity (which may be undesirable) and the space name's type can't be changed based on a condition unless you cast to AnyHashable
anyway (or use some other way of unifying the space types).
Also note that AnyHashable
is quite magical compared to other Any*
type erasers. You get implicit conversion to it:
let space: CoordinateSpace = .named(123) // no `as AnyHashable` needed
and you can cast back from AnyHashable
to the concrete type:
let h: AnyHashable = 123
print(h as? Double) // prints nil
print(h as? Int) // prints Optional(123)
Compare to, for example, AnySequence
. There is no implicit conversion to it:
1> let seq: AnySequence<Int> = [123]
error: repl.swift:1:29: error: cannot convert value of type '[Int]' to specified type 'AnySequence<Int>'
let seq: AnySequence<Int> = [123]
^~~~~
You must manually construct it:
1> let seq: AnySequence<Int> = .init([123])
seq: AnySequence<Int> = { ... }
And you cannot cast back to the concrete type:
2> seq as? [Int]
$R0: [Int]? = nil
Even though AnyHashable
is type erased, it's still type specific you must know its type in order to cast it back to its original type, right?
How does AnyHashable
get this power? NSNumber
also can be casted this way to any numeric types and Bool
. Compiler special bestow magic to these types? Are there more of these magical types?
Without knowing AnyHashable
's special cast ability, I was doing this:
anAnyHashable.base as? Int
now I know it's not necessary to use .base
, just cast directly. Even the the doc on .base is show this.. The doc should make clear cast directly on AnyHashable
is allowed.
dump(anAnyHashable)
â–ż AnyHashable(6)
- value: 6
AnyHashable
has this value
field, and the type is the actual type. So AnyHashable
is very special, like a generic.
The standard library type AnyHashable is written partially in C++
The compiler and the Swift runtime have special support for AnyHashable
.
There are other types that have “magical” treatment in the compiler and/or runtime. Two that I can think of off-hand are Error
and Optional
.
The Error
existential type conforms to the Error
protocol (this conformance was added so that Result<X, Error>
is valid), and the Error
existential has a special single-pointer representation (for bridging with Objective-C's NSError
).
Optional
has too many special treatments to list.
If you define an API in Objective C that uses NSSet *
, then it will be bridged to Swift as Set<AnyHashable>
.
Objective C declaration:
#pragma once
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
void processSet(NSSet * set);
void processDictionary(NSDictionary * dictionary);
NS_ASSUME_NONNULL_END
Bridged Swift equivalent:
processSet(_ set: Set<AnyHashable>)
processDictionary(_ dictionary: [AnyHashable: Any])