Swift 2 - 遵循Equatable协议的问题

3
我有一个问题,涉及到我下面定义的协议。我有两个要求:
  1. 我想在其他类中使用协议 Peer 作为类型,同时保持具体类的私有性。
  2. 我想将协议存储在数组中,并能够确定实例的索引。
为了满足第二点,我需要使该协议符合 Equatable 协议。但是当我这样做时,我不能再将 Peer 用作类型,因为它需要被视为通用类型。这意味着我不能再将具体实现保持私有,而第一条要求也被破坏了。
想知道是否有人遇到过这个问题并以某种方式解决了它。也许我误解了我在 indexOf 处得到的错误... Group.swift
import Foundation

class Group {
    var peers = [Peer]()

    init() {
        peers.append(PeerFactory.buildPeer("Buddy"))
    }

    func findPeer(peer: Peer) -> Bool {
        if let index = peers.indexOf(peer) {
            return true
        }
        return false
    }
}

Peer.swift

import Foundation

protocol Peer {
    var name: String { get }
}

class PeerFactory {
    static func buildPeer(name: String) -> Peer {
        return SimplePeer(name: name)
    }
}

private class SimplePeer: Peer {
    let name: String

    init(name: String) {
        self.name = name
    }
}

如果Peer不是Equatable,则在indexOf处出现错误:

cannot convert value of type 'Peer' to expected argument type '@noescape (Peer) throws -> Bool'

协议数组可能会有问题。你看过关于基于协议的Swift编程的WWDC 2015视频吗? - matt
我几个月前看过它。我会重新观看并查看它是否给我任何线索。 - Mark
“一个采用协议的数组如何才能被视为可比较,这是视频讨论的核心问题。但请注意:协议数组可能会带来问题。” - matt
请查看我的(未回答的)问题:https://dev59.com/wFwY5IYBdhLWcg3wAj09 - matt
2个回答

5

所以我找到了一种解决方法,通过扩展CollectionType来绕过需要Equatable的要求,为元素是Peer类型定义一个新的indexOf,利用了其他基于闭包的indexOf。这本质上是一个方便函数,可以避免我直接使用闭包indexOf。下面是代码:

extension CollectionType where Generator.Element == Peer {
    func indexOf(element: Generator.Element) -> Index? {
        return indexOf({ $0.name == element.name })
    }
}

当然,这是建立在我需要测试相等性的所有内容都可以从Peer协议中获取的前提下(对于我的特定用例来说是真的)。

编辑:Swift 3更新:

extension Collection where Iterator.Element == Peer {
    func indexOf(element: Iterator.Element) -> Index? {
        return index(where: { $0.name == element.name })
    }
}

通常情况下,您需要编写自己的扩展来处理需要类型实现Equatable的任何协议,并手动执行相应的相等性检查。 - Mark
这个答案基本上就是我建议你观看的WWDC 2015视频中给出的解决方案,不是吗? - matt
不完全是这样。他们通过在协议中定义一个isEqual方法来解决“可比较的Drawable”问题,然后为所有既是Drawable又是Equatable的类编写了一个协议扩展来处理比较。我在这里做的是针对我的indexOf方法的特定需求完全避免使用Equatable。这是我参考的视频,解决方案从38:30开始:https://developer.apple.com/videos/play/wwdc2015-408/ - Mark
好的,对于那些没有意识到这是相同想法的人,他们不会看出这是相同的想法。 :) - matt
Swift 3似乎对这个解决方案不满意。有最新的更新吗? - Xvolks
让我今晚看看更新它。 - Mark

1

我建议您使用公共超类,这样该类就可以符合Equatable

class Peer: Equatable {
    // Read-only computed property so you can override.
    // If no need to override, you can simply declare a stored property 
    var name: String {
        get {
            fatalError("Should not call Base")
        }
    }

    // should only be called from subclass
    private init() {}
}

private class SimplePeer: Peer {
    override var name: String {
        get {
            return _name
        }
    }

    let _name: String

    init(name: String) {
        _name = name
        super.init()
    }
}

func == (lhs: Peer, rhs: Peer) -> Bool {
    return lhs.name == rhs.name
}

class PeerFactory {
    static func buildPeer(name: String) -> Peer {
        return SimplePeer(name: name)
    }
}

这也可以工作,尽管必要的fatalError有点设计上的瑕疵。如果我发现有很多需要重写为我的协议的Equatable要求的函数,那么这可能是一个更可行的选项。 - Mark

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