Protocol Type Array

I checked all the answers to this problem in stackoverflow, but still cannot figure out how to fix it. My model looks like this:

protocol Commandable: Equatable { var condition: Condition? {get set} func execute() -> SKAction } 

And 3 structures that implement this protocol

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

The problems begin when I try to create a third structure with an [Commandable] array:

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

Compiler Output: Protocol 'Commandable' can only be used as a generic constraint because it has Self or associated type requirements . Then I rewrote my structure as follows:

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

I solve this problem, but a new problem has appeared. Now I can not create a FunctionCommand with instances of the Rotate and Move command, only with instances of one of them :(:

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

Any help would be appreciated.

Update . This article helped me figure it out - https://krakendev.io/blog/generic-protocols-and-their-shortcomings

+5
source share
3 answers

What you need to do is use style erasure, such as AnyHashable in the Swift standard library.

You can not:

 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 need to do is use the type of the erasable type AnyHashable :

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

So, your solution was to first split the protocol into smaller parts and promote Equatable in 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 structure, for example:

 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 as follows:

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

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

 a.commands[0].condition 

You need to remember that now add Hashable and Equatable to all your teams. I used these 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 } } 
+2
source

I think this is easy to do by introducing your own CustomEquatable protocol.

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

Then you must comply with this protocol, and in addition, it must also comply with 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 } } 

Now you need to connect your CustomEquatable protocol to Swift Equatable through a universal extension:

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

This is not an ideal solution, but now you can store your objects in an array of protocol objects and use the == operator with your objects. For example (several simplified objects):

 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 good practice, especially for performance reasons.

+2
source

I believe that the problem here is that the equivalent protocol has its own requirements. Thus, you can solve your problem by removing the equivalent protocol from your command protocol and make your structures of yours equivalent. This, of course, will limit your protocol, but perhaps this is a compromise, which is reasonable?

0
source

Source: https://habr.com/ru/post/1263826/


All Articles