协议类型

时间:2017-02-04 23:00:01

标签: swift generics

我已经在stackoverflow上检查了有关此问题的所有答案,但仍然无法弄清楚如何解决这个问题。 我的模型看起来像这样

protocol Commandable: Equatable {
    var condition: Condition? {get set}

    func execute() -> SKAction
}

实施该协议的3个结构

struct MoveCommand: Commandable {

    var movingVector: CGVector!

    //MARK: - Commandable

    var condition: Condition?
    func execute() -> SKAction {
       ...
    }
}

extension MoveCommand {
    // MARK:- Equatable

    static func ==(lhs: MoveCommand, rhs: MoveCommand) -> Bool {
        return lhs.movingVector == rhs.movingVector && lhs.condition == rhs.condition
    }
}
struct RotateCommand: Commandable {
    var side: RotationSide!

    // MARK: - Commandable

    var condition: Condition?
    func execute() -> SKAction {
        ...
    }
}

extension RotateCommand {
    // MARK: - Equatable
    static func ==(lhs: RotateCommand, rhs: RotateCommand) -> Bool {
        return lhs.side == rhs.side && lhs.condition == rhs.condition
    }
}

当我尝试创建具有[Commandable]数组的第三个结构时,问题就开始了:

struct FunctionCommand: Commandable {
    var commands = [Commandable]()

编译器输出:Protocol 'Commandable' can only be used as a generic constraint because it has Self or associated type requirements。然后我用这种方式重写了我的结构:

struct FunctionCommand<T : Equatable>: Commandable {
    var commands = [T]()

我解决了这个问题但是出现了新的问题。现在我无法使用旋转和移动命令的实例创建FunctionCommand,只能使用其中一个实例:(:

let f = FunctionCommand(commands: [MoveCommand(movingVector: .zero, condition: nil), 
RotateCommand(side: .left, condition: nil)], condition: nil)

任何帮助都将不胜感激。

更新:那篇文章帮我弄明白了 - https://krakendev.io/blog/generic-protocols-and-their-shortcomings

3 个答案:

答案 0 :(得分:6)

您需要做的是使用类型擦除,就像Swift标准库中的AnyHashable一样。

你做不到:

var a: [Hashable] = [5, "Yo"]
// error: protocol 'Hashable' can only be used as a generic constraint because it has Self or associated type requirements

您需要做的是使用类型删除类型AnyHashable

var a: [AnyHashable] = [AnyHashable(5), AnyHashable("Yo")]
a[0].hashValue // => shows 5 in a playground

因此,您的解决方案是首先将协议拆分为较小的部分,并将Equatable提升为Hashable(以重用AnyHashable

protocol Conditionable {
    var condition: Condition? { get set }
}

protocol Executable {
    func execute() -> SKAction
}

protocol Commandable: Hashable, Executable, Conditionable {}

然后创建一个AnyCommandable结构,如下所示:

struct AnyCommandable: Commandable, Equatable {
    var exeBase: Executable
    var condBase: Conditionable
    var eqBase: AnyHashable

    init<T: Commandable>(_ commandable: T) where T : Equatable {
        self.condBase = commandable
        self.exeBase = commandable
        self.eqBase = AnyHashable(commandable)
    }

    var condition: Condition? {
        get {
            return condBase.condition
        }
        set {
            condBase.condition = condition
        }
    }

    var hashValue: Int {
        return eqBase.hashValue
    }

    func execute() -> SKAction {
        return exeBase.execute()
    }

    public static func ==(lhs: AnyCommandable, rhs: AnyCommandable) -> Bool {
        return lhs.eqBase == rhs.eqBase
    }
}

然后你可以像这样使用它:

var a = FunctionCommand()
a.commands = [AnyCommandable(MoveCommand()), AnyCommandable(FunctionCommand())]

您可以轻松访问commands的属性,因为AnyCommandable实现了Commandable

a.commands[0].condition

您需要记住现在为所有命令添加HashableEquatable。 我使用这些实现进行测试:

struct MoveCommand: Commandable {

    var movingVector: CGVector!

    var condition: Condition?
    func execute() -> SKAction {
        return SKAction()
    }

    var hashValue: Int {
        return Int(movingVector.dx) * Int(movingVector.dy)
    }

    public static func ==(lhs: MoveCommand, rhs: MoveCommand) -> Bool {
        return lhs.movingVector == rhs.movingVector
    }
}

struct FunctionCommand: Commandable {
    var commands = [AnyCommandable]()

    var condition: Condition?

    func execute() -> SKAction {
        return SKAction.group(commands.map { $0.execute() })
    }

    var hashValue: Int {
        return commands.count
    }

    public static func ==(lhs: FunctionCommand, rhs: FunctionCommand) -> Bool {
        return lhs.commands == rhs.commands
    }
}

答案 1 :(得分:2)

我认为通过引入您自己的CustomEquatable协议可以轻松完成。

protocol Commandable: CustomEquatable {
    var condition: String {get}
}

protocol CustomEquatable {
    func isEqual(to: CustomEquatable) -> Bool
}

然后,您的对象必须符合此协议,此外它还应符合Equitable。

struct MoveCommand: Commandable, Equatable {
    let movingVector: CGRect
    let condition: String

    func isEqual(to: CustomEquatable) -> Bool {
        guard let rhs = to as? MoveCommand else { return false }

        return movingVector == rhs.movingVector && condition == rhs.condition
    }
}

struct RotateCommand: Commandable, Equatable {
    let side: CGFloat
    let condition: String

    func isEqual(to: CustomEquatable) -> Bool {
        guard let rhs = to as? RotateCommand else { return false }

        return side == rhs.side && condition == rhs.condition
    }
}

您现在需要做的就是通过通用扩展将 CustomEquatable 协议连接到Swift Equatable

extension Equatable where Self: CustomEquatable {

    static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.isEqual(to: rhs)
    }
}

这不是一个完美的解决方案,但现在,您可以将对象存储在一组协议对象中,并将 == 运算符与您的对象一起使用。例如(我简化了一些对象):

let move = MoveCommand(movingVector: .zero, condition: "some")
let rotate = RotateCommand(side: 0, condition: "some")

var array = [Commandable]()
array.append(move)
array.append(rotate)  

let equal = (move == MoveCommand(movingVector: .zero, condition: "some"))
let unequal = (move == MoveCommand(movingVector: .zero, condition: "other"))
let unequal = (move == rotate) // can't do this, compare different types

PS。在结构上使用var不是一个好习惯,特别是出于性能原因。

答案 2 :(得分:0)

我认为这里的问题是等同协议有自我要求。因此,您可以通过从Commandable协议中删除equatable协议来解决您的问题,并使您的结构相等。这当然会限制你的协议,但也许这是一个合理的权衡吗?