创建一个表示可以打开或关闭的可散列对象的协议

Creating a protocol that represents hashable objects that can be on or off

我正在尝试创建一个简单的协议来说明对象是处于 "on" 状态还是 "off" 状态。对那是什么的解释取决于实施对象。对于 UISwitch,它是开关是打开还是关闭(duh)。对于UIButton,可能是按钮是否处于selected状态。对于 Car,它可能是汽车的引擎是否打开,或者即使它是否在移动。所以我着手创建这个简单的协议:

protocol OnOffRepresentable {
    func isInOnState() -> Bool
    func isInOffState() -> Bool
}

现在我可以像这样扩展上述 UI 控件:

extension UISwitch: OnOffRepresentable {
    func isInOnState() -> Bool { return on }
    func isInOffState() -> Bool { return !on }
}

extension UIButton: OnOffRepresentable {
    func isInOnState() -> Bool { return selected }
    func isInOffState() -> Bool { return !selected }
}

现在我可以创建一个包含这些类型对象的数组并循环检查它们是打开还是关闭:

let booleanControls: [OnOffRepresentable] = [UISwitch(), UIButton()]
booleanControls.forEach { print([=12=].isInOnState()) }

太棒了!现在我想制作一个将这些控件映射到 UILabel 的字典,这样我就可以在控件更改状态时更改与控件关联的标签的文本。所以我去声明我的字典:

var toggleToLabelMapper: [OnOffRepresentable : UILabel] = [:]
// error: type 'OnOffRepresentable' does not conform to protocol 'Hashable'

哦!正确的!傻我。好的,让我使用协议组合更新协议(毕竟,我想在这里使用的控件都是 Hashable:UISwitch、UIButton 等):

protocol OnOffRepresentable: Hashable {
    func isInOnState() -> Bool
    func isInOffState() -> Bool
}

但是现在我得到了一组新的错误:

error: protocol 'OnOffRepresentable' can only be used as a generic constraint because it has Self or associated type requirements
error: using 'OnOffRepresentable' as a concrete type conforming to protocol 'Hashable' is not supported

好的...所以我做了一些堆栈溢出挖掘和搜索。我发现许多文章看起来很有前途,例如 Set and protocols in Swift, , and I see that there are some great articles out there on type erasure that seem to be exactly what I need: http://krakendev.io/blog/generic-protocols-and-their-shortcomings, http://robnapier.net/erasure, and https://realm.io/news/type-erased-wrappers-in-swift/ 仅举几例。

这就是我卡住的地方。我试过通读所有这些内容,并尝试创建一个 class 将是 Hashable 并且也符合我的 OnOffRepresentable 协议,但我不知道如何让一切都连接起来。

我不知道我是否必须让 OnOffRepresentable 协议继承自 Hashable。它看起来不像是您想要表示为打开或关闭的东西 必须 也可以散列。所以在我下面的实现中,我只将 Hashable 一致性添加到类型擦除包装器。这样,您可以尽可能直接引用 OnOffRepresentable 项目(没有 "can only be used in a generic constraint" 警告),并且只在需要将它们放入集合或使用它们时将它们包裹在 HashableOnOffRepresentable 类型的橡皮擦中作为字典键。

protocol OnOffRepresentable {
    func isInOnState() -> Bool
    func isInOffState() -> Bool
}

extension UISwitch: OnOffRepresentable {
    func isInOnState() -> Bool { return on }
    func isInOffState() -> Bool { return !on }
}

extension UIButton: OnOffRepresentable {
    func isInOnState() -> Bool { return selected }
    func isInOffState() -> Bool { return !selected }
}

struct HashableOnOffRepresentable : OnOffRepresentable, Hashable {

    private let wrapped:OnOffRepresentable
    private let hashClosure:()->Int
    private let equalClosure:Any->Bool

    var hashValue: Int {
        return hashClosure()
    }

    func isInOnState() -> Bool {
        return wrapped.isInOnState()
    }

    func isInOffState() -> Bool {
        return wrapped.isInOffState()
    }

    init<T where T:OnOffRepresentable, T:Hashable>(with:T) {
        wrapped = with
        hashClosure = { return with.hashValue }
        equalClosure = { if let other = [=10=] as? T { return with == other } else { return false } }
    }
}

func == (left:HashableOnOffRepresentable, right:HashableOnOffRepresentable) -> Bool {
    return left.equalClosure(right.wrapped)
}

func == (left:HashableOnOffRepresentable, right:OnOffRepresentable) -> Bool {
    return left.equalClosure(right)
}

var toggleToLabelMapper: [HashableOnOffRepresentable : UILabel] = [:]

let anySwitch = HashableOnOffRepresentable(with:UISwitch())
let anyButton = HashableOnOffRepresentable(with:UIButton())

var switchLabel:UILabel!
var buttonLabel:UILabel!

toggleToLabelMapper[anySwitch] = switchLabel
toggleToLabelMapper[anyButton] = buttonLabel

创建带有 associatedType 的协议(或使其符合另一个带有 associatedType 的协议,如 Hashable)将使该协议与泛型非常不友好。

我会向您推荐一个非常简单的解决方法

OnOffRepresentable

首先我们不需要两个完全相反的函数吧? ;)

所以这个

protocol OnOffRepresentable {
    func isInOnState() -> Bool
    func isInOffState() -> Bool
}

变成这样

protocol OnOffRepresentable {
    var on: Bool { get }
}

当然还有

extension UISwitch: OnOffRepresentable { }

extension UIButton: OnOffRepresentable {
    var on: Bool { return selected }
}

将 OnOffRepresentable 与 UILabel 配对

现在我们不能将 OnOffRepresentable 用作 DictionaryKey,因为我们的协议必须是 Hashable。那我们换个数据结构吧!

let elms: [(OnOffRepresentable, UILabel)] = [
    (UISwitch(), UILabel()),
    (UIButton(), UILabel()),
]

就是这样。