是否可以在Swift中创建异构集?

时间:2017-08-13 06:21:24

标签: swift set

考虑这个例子:

protocol Observable: Hashable {
    // ...
}

struct People: Observable {
    var name: String
    var age: Double

    var hashValue: Int {
        // ...
    }

    static func ==(lhs: People, rhs: People) -> Bool {
        // ,,,
    }
}

struct Color: Observable {
    var red: Double, green: Double, blue: Double

    var hashValue: Int {
        // ...
    }

    static func ==(lhs: Color, rhs: Color) -> Bool {
        // ...
    }
}

var observers: Set<Observable> = [] // Not allowed by the compiler

People和Color都符合Observable协议,该协议也继承自Hashable协议。我想将这些内容存储在observers集。

using 'Observable' as a concrete type conforming to protocol 
'Hashable' is not supported

是否有可能在Swift中做异类Set?

2 个答案:

答案 0 :(得分:2)

有一种方法可以实现。(受Apple的实施启发)

在开始之前,这就是我们想要构建的内容。

protocol Observer: Hashable {
    associatedtype Sender: Observable

    func valueDidChangeInSender(_ sender: Sender, keypath: String, newValue: Any)
}

此问题的根源是使用强制数组为同源的Self。你可以在这里看到它:

without self and with self comparison

最重要的变化是它阻止协议作为一种类型使用。

这使我们无法做到:

var observers: [Observer] = [] // Observer is not usable as a type.

因此,我们需要另一种方法来使其发挥作用。

我们不做

var observers: [AnyHashable] = []

因为AnyHashable不会将对象约束为符合Observer协议。相反,我们可以将Observer对象包装在AnyObserver包装器中,如下所示:

var observers: [AnyObserver] = []
observers.append(AnyObserver(yourObject))

这将确保AnyObserver struct的值符合Observer协议。

根据WWDC 2015:Swift中面向协议的编程,我们可以使用isEqual(_:)方法建立桥梁,以便我们可以比较两个Any。这样,对象就不必符合Equatable协议。

protocol AnyObserverBox {
    var hashValue: Int { get }
    var base: Any { get }

    func unbox<T: Hashable>() -> T

    func isEqual(to other: AnyObserverBox) -> Bool
}

之后,我们制作符合 AnyObserverBox的框。

struct HashableBox<Base: Hashable>: AnyObserverBox {
    let _base: Base

    init(_ base: Base) {
        _base = base
    }

    var base: Any {
        return _base
    }

    var hashValue: Int {
        return _base.hashValue
    }

    func unbox<T: Hashable>() -> T {
        return (self as AnyObserverBox as! HashableBox<T>)._base
    }

    func isEqual(to other: AnyObserverBox) -> Bool {
        return _base == other.unbox()
    }
}

此框包含我们稍后将创建的AnyObserver的实际值。

最后我们制作 AnyObserver

struct AnyObserver {
    private var box: AnyObserverBox

    public var base: Any {
        return box.base
    }

    public init<T>(_ base: T) where T: Observer {
        box = HashableBox<T>(base)
    }
}

extension AnyObserver: Hashable {
    static func ==(lhs: AnyObserver, rhs: AnyObserver) -> Bool {
        // Hey! We can do a comparison without Equatable protocol.
        return lhs.box.isEqual(to: rhs.box)
    }

    var hashValue: Int {
        return box.hashValue
    }
}

完成所有这些后,我们可以:

var observers: [AnyObserver] = []
observers.append(AnyObserver(yourObject))

答案 1 :(得分:0)

实际上,你不能声明一个Set -or甚至是一个类型为Observable的数组,这是因为在某个级别Observable代表一个通用协议:

可观察 - &gt; Hashable - &gt;的 Equatable

包含:

  

public static func ==(lhs: Self ,rhs: Self ) - &gt;布尔

这就是为什么无法以 Heterogenous 方式使用它的原因。此外,您不能声明存在类型:

var object: Observable?
// error: protocol 'Observable' can only be used as a generic constraint
// because it has Self or associated type requirements

如果您想知道这种约束的原因是什么,我认为比较拖曳People或拖曳Color是合乎逻辑的,但比较{{1}与People

那么,我们能做些什么呢?

作为一种解决方法,您可以将您的设置设为AnyHashable结构集(如评论中提到的@Leo):

  

AnyHashable类型转发相等比较和散列   对基础hashable值的操作,隐藏其特定的   基础类型。

如下:

Color

这是合法的。