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

Edw*_*ony 0 set swift

考虑这个例子:

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
Run Code Online (Sandbox Code Playgroud)

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

using 'Observable' as a concrete type conforming to protocol 
'Hashable' is not supported
Run Code Online (Sandbox Code Playgroud)

是否可以在 Swift 中进行异构 Set?

Edw*_*ony 5

There is a way to make it possible. (Inspired by Apple's implementation)

Before we begin, this is what we want to build.

protocol Observer: Hashable {
    associatedtype Sender: Observable

    func valueDidChangeInSender(_ sender: Sender, keypath: String, newValue: Any)
}
Run Code Online (Sandbox Code Playgroud)

The source of this problem is the use of Self that force the array to be Homogenous. You can see it here:

without self and with self comparison

The most important change is that it stop the protocol from being usable as a type.

That makes us can't do:

var observers: [Observer] = [] // Observer is not usable as a type.
Run Code Online (Sandbox Code Playgroud)

Therefore, we need another way to make it work.

We don't do

var observers: [AnyHashable] = []
Run Code Online (Sandbox Code Playgroud)

Because AnyHashable will not constrain the object to conform Observer protocol. Instead, we can wrap the Observer object in the AnyObserver wrapper like this:

var observers: [AnyObserver] = []
observers.append(AnyObserver(yourObject))
Run Code Online (Sandbox Code Playgroud)

This will make sure the value of AnyObserver struct conforms to Observer protocol.

根据 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
}
Run Code Online (Sandbox Code Playgroud)

之后,我们制作符合 的盒子 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()
    }
}
Run Code Online (Sandbox Code Playgroud)

此框包含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
    }
}
Run Code Online (Sandbox Code Playgroud)

有了所有这些,我们可以做到:

var observers: [AnyObserver] = []
observers.append(AnyObserver(yourObject))
Run Code Online (Sandbox Code Playgroud)