Is Swift bothering you with those pesky error messages? Something something about associated type requirements. Like when you want to cast something to a protocol, but then:
Well not to worry. Introducing AssociatedTypeRequirementsKit 🤗 A collection of µFrameworks to help you get around those annoying scenarios!
Our example from before is dealt like so:
import AssociatedTypeRequirementsVisitor
private let hasher = AnyHasher()
func hashValue(value: Any) -> Int {
// There's a function `AnyHasher.callAsFunction(_:Any) -> Int?`
return hasher(value) ?? 0
}
struct AnyHasher: HashableVisitor {
// This function will called by `callAsFunction(_:Any)` if the input conforms to hashable
func callAsFunction<T : Hashable>(_ value: T) -> Int {
return value.hashValue
}
}
You can install AssociatedTypeRequirementsKit via Swift Package Manager by adding the following line to your Package.swift
:
import PackageDescription
let package = Package(
[...]
dependencies: [
.package(url: "https://github.com/nerdsupremacist/AssociatedTypeRequirementsKit.git", from: "0.1.0")
]
)
If you want to be able to call a function on a protocol with associated types, then you'll have to provide a generic function. Since closures cannot be generic, we have to use a protocol to encode this.
For example if you want to be able to turn any SwiftUI view into an AnyView, but you don't know the type at compile time, you can use ViewVisitor
:
import AssociatedTypeRequirementsVisitor
import SwiftUI
private let converter = AnyViewConverter()
extension AnyView {
init?(_ value: Any) {
guard let view = converter(value) else { return nil }
self = view
}
}
private struct AnyViewConverter : ViewVisitor {
// Provide a function that can be called with all the necessary type information
func callAsFunction<T : View>(_ value: T) -> AnyView {
return AnyView(value)
}
}
But why would you need to do this? Well, for example if you want to get all the subviews of a tuple view?
extension TupleView {
func subviews() -> [AnyView] {
let mirror = Mirror(reflecting: self)
let tuple = mirror.children.first!.value
let tupleMirror = Mirror(reflecting: tuple)
return tupleMirror.children.map { AnyView($0.value)! }
}
}
ViewVisitor
is available out of the box because we are already shipping visitor protocols for the most important problematic protocols in Swift right now, and are extending the list as we go.
If you have to handle your own protocol you can do it as in the following example:
protocol MyProtocolVisitor: AssociatedTypeRequirementsVisitor {
associatedtype Visitor = MyProtocolVisitor
associatedtype Input = MyProtocol
associatedtype Output
func callAsFunction<T : MyProtocol>(_ value: T) -> Output
}
If you don't want to use the AssociatedTypeRequirementsVisitor
API, you can also ask use the low level withCasted
API and ask for the protocol conformance yourself.
import Casting
func test(value: Any) -> AnyHashable? {
return withCasted(value, as: .hashable) { casted in
// casted is CastedProtocolValue
...
}
}
But what is this CastedProtocolValue
? Well it's a little struct that has the same layout that a function func anyHashable<T : Hashable>(hashable: T)
would expect. So that function could be casted:
import Casting
func test(value: Any) -> AnyHashable? {
return withCasted(value, as: .hashable) { casted in
// A pointer to the function is in `functionPointer`
let function = unsafeBitCast(functionPointer, (@convention(thin) (CastedProtocolValue) -> AnyHashable).self)
return function(casted)
}
}
When you use the withUnsafePointer
API from the Swift Standard Library, but with Any
you'll notice that the pointer or bytes you get are not quite correct.
That's becasue they're pointing to the existential container Any
, which is always 32 Bytes.
That's why we ship withUnsafeValuePointer
which will always point to the actual value, instead of the container:
import ValuePointers
struct MyStruct {
let first: String
let second: String
}
let value = MyStruct(first: "A", second: "B") as Any
let secondString = withUnsafeValuePointer(to: value) { $0.assumingMemoryBound(to: String.self).advanced(by: 1).pointee }
// "B"
print(secondString)
Whenever you want to access the meta-type of a protocol with associated types, you'll run into this exact same problem.
You can use ProtocolType
to access it via the name of the protocol:
import ProtocolType
let protocolType = ProtocolType(moduleName: "SwiftUI", protocolName: "View")
print(protocolType?.type) // SwiftUI.View.self
And ProtocolType provides a set of constants already shipped. The list of constants can be changed in the commonProtocols.json
file and can be extended further:
import ProtocolType
func hash(protocol protocolType: ProtocolType) -> some Hashable {
return unsafeBitCast(type, as: Int.self)
}
let hashed = hash(protocol: .collection)
Contributions are welcome and encouraged!
AssociatedTypeRequirementsKit is available under the MIT license. See the LICENSE file for more info.