AssociatedTypeRequirementsKit

Swift 总是因为恼人的错误信息困扰你吗?总是关于关联类型需求的问题。比如你想把某个东西转换为一个协议,但是......

别担心。现在介绍 AssociatedTypeRequirementsKit 🤗,一个 µFrameworks 的集合,可以帮助你解决这些恼人的场景!

我们之前的例子可以这样处理:

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 
    }
}

安装

Swift 包管理器

你可以通过 Swift 包管理器 安装 AssociatedTypeRequirementsKit,只需将以下行添加到你的 Package.swift 文件中:

import PackageDescription

let package = Package(
    [...]
    dependencies: [
        .package(url: "https://github.com/nerdsupremacist/AssociatedTypeRequirementsKit.git", from: "0.1.0")
    ]
)

使用

AssociatedTypeRequirementsVisitor

如果你想在一个带有关联类型的协议上调用一个函数,那么你必须提供一个泛型函数。由于闭包不能是泛型的,我们必须使用一个协议来编码它。

例如,如果你想将任何 SwiftUI 视图转换为 AnyView,但在编译时不知道类型,你可以使用 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)
    }
}

但是你为什么要这样做呢? 例如,如果你想获取元组视图的所有子视图呢?

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 是开箱即用的,因为我们已经在为 Swift 中最重要的有问题的协议提供 visitor 协议,并且正在不断扩展列表。 如果你需要处理自己的协议,你可以按照以下示例进行操作:

protocol MyProtocolVisitor: AssociatedTypeRequirementsVisitor {
    associatedtype Visitor = MyProtocolVisitor
    associatedtype Input = MyProtocol
    associatedtype Output
    
    func callAsFunction<T : MyProtocol>(_ value: T) -> Output
}

类型转换

如果你不想使用 AssociatedTypeRequirementsVisitor API,你也可以使用底层的 withCasted API 并自行请求协议一致性。

import Casting

func test(value: Any) -> AnyHashable? {
    return withCasted(value, as: .hashable) { casted in 
        // casted is CastedProtocolValue
        ...
    }
}

但这个 CastedProtocolValue 是什么呢? 这是一个小结构体,它具有与函数 func anyHashable<T : Hashable>(hashable: T) 期望的相同的布局。 所以该函数可以被强制转换。

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)
    }
}

ValuePointers (值指针)

当你使用 Swift 标准库中的 withUnsafePointer API,但使用 Any 时,你会注意到你获得的指针或字节不太正确。 这是因为它们指向存在性容器 Any,它总是 32 字节。

这就是为什么我们提供 withUnsafeValuePointer,它将始终指向实际值,而不是容器。

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)

ProtocolType (协议类型)

每当你想访问带有关联类型的协议的元类型时,你都会遇到完全相同的问题。 你可以使用 ProtocolType 通过协议名称来访问它。

import ProtocolType

let protocolType = ProtocolType(moduleName: "SwiftUI", protocolName: "View")
print(protocolType?.type) // SwiftUI.View.self

ProtocolType 提供了已经提供的常量集合。 常量列表可以在 commonProtocols.json 文件中更改,并且可以进一步扩展。

import ProtocolType

func hash(protocol protocolType: ProtocolType) -> some Hashable {
    return unsafeBitCast(type, as: Int.self)
}

let hashed = hash(protocol: .collection)

贡献

欢迎并鼓励贡献!

许可证

AssociatedTypeRequirementsKit 在 MIT 许可证下可用。 有关更多信息,请参阅 LICENSE 文件。