【问题标题】:Is it possible to make a Heterogeneous Set in Swift?是否可以在 Swift 中创建异构集合?
【发布时间】:2017-08-13 21:42:16
【问题描述】:

考虑这个例子:

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?

【问题讨论】:

  • 你为什么不直接使用一组AnyHashable
  • “如果我使用 Array 执行此操作,它可以工作”,我收到以下错误:error: protocol 'Observable' can only be used as a generic constraint because it has Self or associated type requirements var observers: Array&lt;Observable&gt; = []
  • @AhmadF 你是对的,对此感到抱歉。我编辑了这个问题。但是如何制作 Heterogenous Set 呢?
  • var observers: Set&lt;AnyHashable&gt; = []
  • 问题是(在链接的问答中)您可以比较两个人或两种颜色,但不能比较一个人与一种颜色。

标签: swift set


【解决方案1】:

有办法让它成为可能。(受 Apple 实施的启发)

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

protocol Observer: Hashable {
    associatedtype Sender: Observable

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

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

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

这让我们做不到:

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: Protocol-Oriented Programming in 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))

【讨论】:

  • 这与stackoverflow.com/q/43263352/2976878 中显示的技术相同,一种类型橡皮擦(在这种情况下可能与欺骗一样接近?)。尽管请注意,当您强制转换为 HashableBox&lt;T&gt; 时,尝试在具有不同基本类型的两个 AnyObserver 实例上使用 == 时,您当前的实现会崩溃。您应该在那里使用条件转换(即as? 并返回T?)。
  • 我使用了与 Apple AnyHashable 在 Swift 开源中展示的相同技术。我并不是要复制您的答案,因为在创建此答案时我没有看到您的答案。没错,它会崩溃,感谢您的反馈。
  • 我并不是说您复制了我的答案(远非如此;正如您所指出的,您的实现基于 Swift 的 AnyHashable 实现,使用协议进行类型擦除,而我的使用闭包) :) 我只是说它们都是键入擦除符合给定协议的 Hashable 实例的底层具体类型的有效方法(可能可以更好地表达我的第一条评论)。
【解决方案2】:

实际上,您不能声明 Observable 类型的 Set(甚至是数组),这是因为 Observable 在某种程度上代表了一个通用协议:

Observable -> Hashable -> Equatable

其中包含:

public static func ==(lhs: Self, rhs: Self) -> Bool

这就是无法以异构方式使用它的原因。此外,您不能声明 existential 类型:

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

如果您想知道这个约束的原因是什么,我认为比较两个People 或两个Color 是合乎逻辑的,但比较PeopleColor

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

作为一种解决方法,您可以让您的集合成为一组AnyHashable 结构(正如评论中提到的@Leo):

AnyHashable 类型转发相等比较和散列 对底层可哈希值的操作,隐藏其特定的 底层类型。

如下:

let people = People(name: "name", age: 101)
let color = Color(red: 101, green: 101, blue: 101)

var observers: Set<AnyHashable> = []

observers.insert(people)
observers.insert(color)

for (index, element) in observers.enumerated() {
    if element is People {
        print("\(index): people")
    }
}

这是合法的。

【讨论】:

  • 这打破了 Set 应该只包含符合 Observable 协议的 Object 的约束。最接近的方法是创建一个名为 AnyObservable 的新结构,它符合 Observable 协议。
猜你喜欢
  • 2016-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-10
  • 1970-01-01
  • 2020-09-24
  • 2017-05-24
  • 1970-01-01
相关资源
最近更新 更多