Array of protocol type

前端 未结 3 1412
感动是毒
感动是毒 2021-02-15 11:24

I have checked all answers about this problem on stackoverflow, but still can not figure out how to fix this. My model looks like this

protocol Commandable: Eq         


        
相关标签:
3条回答
  • 2021-02-15 11:42

    I believe the problem here is that the equatable protocol has self requirements. So you can solve you problem by removing equatable protocol from your Commandable protocol and make your your structs equatable instead. This will of course limit your protocol but maybe it is a trade-off that is reasonable?

    0 讨论(0)
  • 2021-02-15 11:44

    I think it can be easily done by introduction of your own CustomEquatable protocol.

    protocol Commandable: CustomEquatable {
        var condition: String {get}
    }
    
    protocol CustomEquatable {
        func isEqual(to: CustomEquatable) -> Bool
    }
    

    Then, you objects have to conform to this protocol and additionally it should conform Equitable as well.

    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
        }
    }
    

    All you need to do now is connect your CustomEquatable protocol to Swift Equatable through generic extension:

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

    It's not a perfect solution, but now, you can store your objects in a array of protocol objects and use == operator with your objects as well. For example(I simplified objects a little bit):

    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. Using var on struct is not a good practice, especially for performance reasons.

    0 讨论(0)
  • 2021-02-15 11:45

    What you need to do is to use type erasure, much like AnyHashable does in the Swift Standard Library.

    You can't do:

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

    What you have to do is to use the type-erased type AnyHashable:

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

    So your solution would be to first split the protocol in smaller parts and promote Equatable to Hashable (to reuse AnyHashable)

    protocol Conditionable {
        var condition: Condition? { get set }
    }
    
    protocol Executable {
        func execute() -> SKAction
    }
    
    protocol Commandable: Hashable, Executable, Conditionable {}
    

    Then create an AnyCommandable struct, like this:

    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
        }
    }
    

    And then you can use it like this:

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

    And you can easily access properties of commands, because AnyCommandable implements Commandable

    a.commands[0].condition
    

    You need to remember to now add Hashable and Equatable to all your commands. I used those implementations for testing:

    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
        }
    }
    
    0 讨论(0)
提交回复
热议问题