创建一个代表可开关的可哈希对象的协议

12

我正在尝试创建一个简单的协议,用于确定对象是处于“开”状态还是“关”状态。对于实现对象,其解释取决于该对象。对于UISwitch,它表示开关是否打开或关闭(显然)。对于UIButton,它可以表示按钮是否处于selected状态。对于Car,它可以表示汽车引擎是否开启,甚至可以表示汽车是否在移动。因此,我开始创建这个简单的协议:

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

现在我可以创建一个这些对象的数组,并循环检查它们是否打开或关闭:

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

太好了!现在我想要创建一个字典,将这些控件与一个UILabel相映射,以便当控件状态改变时,我可以更改与控件关联的标签文本。所以我开始声明我的字典:

var toggleToLabelMapper: [OnOffRepresentable : UILabel] = [:]
// error: type 'OnOffRepresentable' does not conform to protocol '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

好的...所以我进行了一些堆栈溢出的挖掘和搜索。我发现了许多看起来很有前途的文章,比如Swift中的Set和协议使用某些协议作为符合另一个协议的具体类型不受支持,而且我看到有一些很棒的类型擦除文章,似乎正是我需要的:http://krakendev.io/blog/generic-protocols-and-their-shortcomingshttp://robnapier.net/erasure,以及https://realm.io/news/type-erased-wrappers-in-swift/ 只是其中几个。

但这就是我卡住的地方。我尝试阅读所有这些文章,也尝试着创建一个类,使其同时符合Hashable和我的OnOffRepresentable协议,但我无法弄清如何将它们联系起来。


我最初尝试模仿krakendev的版本,但卡在了他只尝试符合'MythicalType'协议这一事实上。因此,当我尝试将'MythicalType'直接替换为我的'OnOffRepresentable'时,这还不够。我无法想出如何将Hashable部分插入其中。Rob Napier的博客也是一样...也许。他的'Animal'协议是在一个'Food'关联类型上通用的,但这也不是我正在做的。realm.io的示例似乎是最好的选择,但'SequenceType'也与我的特定需求非常不同。 - Tim Fuqua
2个回答

3

我不确定是否有必要让OnOffRepresentable协议继承自Hashable。似乎并不是所有被表示为开或关的对象都需要成为可哈希的。因此,在下面的实现中,我只将Hashable一致性添加到类型擦除包装器中。这样,您可以直接引用OnOffRepresentable项目(而不会出现“仅可在通用约束中使用”警告),只有在需要将它们放入集合或用作字典键时才将其包装在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 = $0 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

这非常接近了。我认为equalClosure有些问题。如果你实例化了实际的UILabels并将它们存储在字典中,然后尝试提取刚刚存储的标签,它总是为空。这让我觉得==运算符没有按预期工作。 - Tim Fuqua
@TimFuqua。干得好!我修复了“==”函数并添加了一个额外的重载,因此您可以直接与协议的未包装实例进行比较。我认为这应该可以解决问题,但我正在手机上输入此内容,尚未在游乐场中验证。 - Daniel Hall
我们完成了!是的,我想这只是一个简单的修复,但我不确定具体是什么。非常好的答案。这是一个很好、直接的方法和示例,将来可以再次参考。 - Tim Fuqua

0

创建一个带有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()),
]

就是这样。


从一开始,我就知道我已经可以做一个OnOffRepresentables数组。虽然那不是重点。我需要它们成为Hashable类型,以便在字典中轻松查找。线性搜索一对数组以找到确切的OnOffRepresentable控件并不理想,也不具有可扩展性。这就是为什么示例中提到了字典的原因。对于将两个函数更改为两个计算属性,我持中立态度(是的,两个,因为我是那种更喜欢使用off而不是!on的开发人员,所以我还会实现一个off属性)。 - Tim Fuqua

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接