2017-02-04 13 views
6

Sprawdziłem wszystkie odpowiedzi na temat tego problemu na stackoverflow, ale nadal nie mogę wymyślić, jak to naprawić. Mój model wygląda takTablica typu protokołu

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

    func execute() -> SKAction 
} 

i 3 strukturach, które realizują ten protokół

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

Problemy zaczynają gdy próbuję utworzyć trzecią strukturę, która ma tablicę z [Commandable]:

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

Dane wyjściowe kompilatora: Protocol 'Commandable' can only be used as a generic constraint because it has Self or associated type requirements. Następnie przepisałem swoją strukturę w następujący sposób:

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

Rozwiązuję ten problem, ale pojawił się nowy problem. Teraz nie mogę tworzyć FunctionCommand z wystąpień obracać i polecenie Przenieś, tylko z instancjami jednej z nich :(:

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

Każda pomoc będzie mile widziane

Aktualizacja. Ten artykuł bardzo mi pomógł wymyślić - https://krakendev.io/blog/generic-protocols-and-their-shortcomings

+0

prostu ciekawi, dlaczego używasz konstrukcjom i protokoły gdy instacji byłoby łatwo rozwiązać ten problem? – Carter

+1

@Carter Swift nie ma klas abstrakcyjnych, także Swift jest językiem zorientowanym na protokół, dlatego próbuję to zrobić za pomocą protokołu. –

+0

Powiązane: [Operacja na tablicy struktur implementujących Równoważne] (http://stackoverflow.com/q/41298464/2976878) – Hamish

Odpowiedz

2

Co należy zrobić, to użyć funkcji usuwania typów, podobnie jak w przypadku biblioteki standardowej Swift, np. AnyHashable.

nie można zrobić:

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

Co trzeba zrobić, to użyć typu-skasowane typ AnyHashable:

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

Więc rozwiązaniem byłoby najpierw podzielić protokołu w mniejsze części i promowania Equatable do Hashable (ponowne wykorzystanie AnyHashable)

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

protocol Executable { 
    func execute() -> SKAction 
} 

protocol Commandable: Hashable, Executable, Conditionable {} 

Następnie utwórz AnyCommandable struct, tak:

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

I wtedy można go używać tak:

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

I można łatwo uzyskać dostęp do właściwości z commands, ponieważ AnyCommandable realizuje Commandable

a.commands[0].condition 

Należy pamiętać, aby dodać teraz Hashable i Equatable do wszystkich poleceń. Kiedyś te implementacje badania:

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

Właściwie prawdopodobnie można połączyć 'exeBase' i' condBase' w jednej zmiennej, o ile żaden protokół nie użył 'Self' w żadnej deklaracji metody. – Alistra

0

Uważam, że problem polega na tym, że równoważny protokół ma własne wymagania, więc możesz rozwiązać problem, usuwając równoważny protokół z protokołu, który można wytłumaczyć, i uczynić swoje struktury odpowiednikami zamiast tego. z twoim protokołem, ale może jest to uzasadnione?

+0

Jeśli wartość Równoważna zostanie usunięta z polecenia, to nie mogę po prostu sprawdzić elementów za pomocą ==, zamiast tego Będę musiał napisać przełącznik lub jeśli do sprawdzenia typu. –

+0

@ BohdanSavych Tak, to prawda, ale to, czego chcesz, nie jest możliwe i nie może działać. Co powiesz na to, że masz równą metodę, która może być określana jako parametr protokołu? Jest to obejście, które może zaspokoić Twoje potrzeby? – Cenny

2

Myślę, że można to łatwo zrobić, wprowadzając własny protokół CustomEquatable.

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

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

Następnie obiekty muszą być zgodne z tym protokołem, a dodatkowo powinny również być zgodne z przepisami 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 
    } 
} 

Wszystko, co musisz zrobić, to podłączyć CustomEquatable protokół Swift Equatable poprzez rozszerzenie Generic:

extension Equatable where Self: CustomEquatable { 

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

Nie jest to idealne rozwiązanie, ale teraz można przechowywać swoje obiekty w tablicy obiektów protokołu i użyj operatora również z twoimi obiektami. Na przykład (trochę uprościliśmy obiekty):

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. Używanie var na struct nie jest dobrą praktyką, szczególnie ze względu na wydajność.