创建表示可以打开或关闭的可打包对象的协议

时间:2016-08-19 23:29:40

标签: ios swift protocols type-erasure hashable

我正在尝试创建一个简单的协议,说明对象是处于“开启”状态还是“关闭”状态。对此的解释取决于实施对象。对于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($0.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 SwiftUsing some protocol as a concrete type conforming to another protocol is not supported,我发现type erasure上有一些好文章似乎正是我需要的:{{3 }},http://krakendev.io/blog/generic-protocols-and-their-shortcomingshttp://robnapier.net/erasure仅举几例。

这是我陷入困境的地方。我已经尝试过阅读所有这些内容,并且我已经尝试创建一个类Hashable并且也符合我的OnOffRepresentable协议的类,但我无法弄清楚如何完成所有这些连接。

2 个答案:

答案 0 :(得分:3)

我不知道我是否必须使OnOffRepresentable协议继承自Hashable。它似乎不是你想要表示为开启或关闭的东西必须也是可以清除的。因此,在下面的实现中,我只将Hashable一致性添加到类型擦除包装器中。这样,您可以尽可能直接引用OnOffRepresentable项(没有“只能在通用约束中使用”警告),并且只在需要放置它们时将它们包装在HashableOnOffRepresentable类型的橡皮擦中in sets或将它们用作字典键。

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

答案 1 :(得分:0)

使用associatedType创建协议(或使其与associatedType Hashable之类的protocol OnOffRepresentable { func isInOnState() -> Bool func isInOffState() -> Bool } 符合其他协议的协议将使该协议与泛型非常不相关。

我建议你一个非常简单的解决方法

OnOffRepresentable

首先,我们不需要2个完全相反的功能? ;)

所以这个

protocol OnOffRepresentable {
    var on: Bool { get }
}

变成这个

extension UISwitch: OnOffRepresentable { }

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

当然

OnOffRepresentable

将OnOffRepresentable与UILabel配对

现在我们无法将Key用作Dictionary的{​​{1}},因为我们的协议必须是Hashable。然后让我们使用另一种数据结构!

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

那就是它。