I’ve just updated to Xcode 16 and my project is littered with warnings due to the change to mark AnyHashable
as not-sendable. I was relying on AnyHashable throughout my app, creating a ViewModel layer that returned a datasource of type UITableViewDiffableDataSource<Int, AnyHashable>
. Now i’m trying to build something that is Hashable and Sendable, and can be slotted in without much refactoring. Going off other examples online I created this new struct modeled after AnyHashable
public struct AnyHashableSendable: Hashable, Sendable {
public let base: any Hashable & Sendable
public init<H>(_ base: H) where H : Hashable {
self.base = base
}
public static func == (lhs: Self, rhs: Self) -> Bool {
AnyHashable(lhs.base) == AnyHashable(rhs.base)
}
public func hash(into hasher: inout Hasher) {
base.hash(into: &hasher)
}
}
But this current implementation comes with a few annoyances that i’m looking to resolve in a way that matches the apple implementation
-
If I have a variable
foo
of typeAnyHashable
and I want to cast it, I can simply dolet newBar = foo as? bar
. But with a variable of typeAnyHashableSendable
I now must call the propertybase
to achieve the same, like solet newBar = foo.base as? bar
. How does the inbuilt AnyHashable avoid the need to call itsbase
property? -
If I define an array or 2d array of AnyHashable (
[AnyHashable]
,[[AnyHashable]]
) I can append any struct that conforms to hashable and it will automatically cast it. E.g.
var array: [AnyHashable] = []
array.append(FooBar(prop1: "test", prop2: "test"))
But with AnyHashableSendable, each item must be initialised as the new object type, e.g.
var array: [AnyHashableSendable] = []
array.append(.init(FooBar(prop1: "test", prop2: "test")))
I’ve tried to solve at least the second one by creating my own custom collection type, with overridden functions that accept AnyHashable and simply call the init and store it. While it works, the differences require a decent bit of refactoring and annoying custom code/types instead of the existing logic were used too. Is there a way i’m unaware of to solve these two issues so that my new type functions like the existing AnyHashable?
2
Answers
It is not possible to create such a type yourself. Both behaviours you mentioned (downcasting directly, and implicit conversion to
AnyHashable
) are built into the compiler directly.From the documentation on dynamic casting in the Swift GitHub repo,
AnyHashable
behaves like an existential type in the context of casting, even though it is astruct
. The cast operator can "reach inside" the existential "box" and get the actual value wrapped in there.You can find a special case for
AnyHashable
in DynamicCast.cpp, there is a call to_swift_anyHashableDownCastConditionalIndirect
. This function is where the "reaching in to the box" happens. See its declaration in AnyHashable.swift.For the implicit conversion to
AnyHashable
, the compiler inserts a compiler intrinsic function called_convertToAnyHashable
, which is also declared in AnyHashable.swift, and I think this is the line that emits it.I encountered the same issue, and I came up with this solution:
So, the assumption is, that when we initialise a
AnyHashable
with a value conforming toHashable
andSendable
, then the resulting "Box" (AnyHashable) becomes Sendable as well.I appreciate comments, whether this is legal, or when there are potential pitfalls with this solution.