Swift 3 introduced AnyHashable
, which is a type-erased box around a Hashable. It solves the problem of how to store objects of different types that conform to the same protocol when the protocol requires an associated type.
When creating an AnyHashable
box, you have to provide the thing you want to box in the initializer:
let someHashable: AnyHashable = AnyHashable(1)
However, you can also use AnyHashable as if you were using a protocol without actually creating anything. It's as if Apple overloaded the assignment operator to do automatic boxing:
let someHashable: AnyHashable = 1
My question is, how is this possible? I have followed several of the guides online about how to create my own type-erased boxes. https://www.bignerdranch.com/blog/breaking-down-type-erasures-in-swift/ However it seems that there is something else going on with the way Apple is doing it.
If it was simply a type-erased box, you would still need to provide the type somewhere, like so:
class MyClass<T> where T: Hashable {
let someHashable: AnyHashable<T>
}
Otherwise the compiler would complain: "requires arguments in <...>"
.
Yet I can simply create a variable:
let someHashable: AnyHashable
I ask because I am implementing a delegate that uses an associated type. I created a type-erased box for it, however I'd like to hide the fact that there is type-erasure going on underneath (similar to the way AnyHashable
appears to work).
Here is my implementation (ignoring the requirements for weak
properties for now).
// The delegate protocol
public protocol SomeDelegate {
associatedtype T
func doSomething(_ t: T)
}
// Type-erasure boilerplate
fileprivate class _AnySomeDelegateBase<T>: SomeDelegate {
init() {
guard type(of: self) != _AnySomeDelegateBase.self else {
fatalError("Must subclass")
}
}
func doSomething(_ t: T) {
fatalError("Must override")
}
}
fileprivate final class _AnySomeDelegateBox<SD: SomeDelegate> : _AnySomeDelegateBase<SD.T> {
private var concrete: SD
init(_ concrete: SD) {
self.concrete = concrete
}
override func doSomething(_ t: T) {
self.concrete.doSomething(t)
}
}
// Type-erasure wrapper
public final class AnySomeDelegate<T>: SomeDelegate {
private let box: _AnySomeDelegateBase<T>
public init<SD: SomeDelegate>(_ base: SD) where SD.T == T {
self.box = _AnySomeDelegateBox(base)
}
public func doSomething(_ t: T) {
self.box.doSomething(t)
}
}
And here is how I'd like to use it:
class MyViewController: UIViewController {
var delegate: AnySomeDelegate?
}
But this won't work: AnySomeDelegate requires arguments in <...>
. Which means I'd also have to parameterize the UIViewController
around T
.
class MyViewController<T>: UIViewController {
var delegate: AnySomeDelegate<T>?
}