Dictionary of a protocol Swift 4

时间:2018-03-25 19:09:47

标签: swift dictionary protocols swift4

I have a protocol called playable which requires implementation of func play(). Class Damage and DrawACard both conform to the protocol

protocol Playable: class {
    func play(game: Game, value: Int) -> Player
}


class Damage: Playable, Hashable, Equatable {

    var hashValue: Int = 1

    static func ==(lhs: Damage, rhs: Damage) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }

    func play(game: Game, value: Int) -> Player {
        // do stuff
        return game.activePlayer
    }
}

class DrawACard: Playable, Equatable, Hashable {

    var hashValue: Int = 2

    static func ==(lhs: DrawACard, rhs: DrawACard) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }

    func play(game: Game, value: Int) -> Player {
        // do stuff
        return game.activePlayer
    }
}

My issue is the following:

class Card {
    var id: Int
    var name: String
    var cost: Int
    var description: String
    var target: Target
    var abilities: [Playable: Int]
    var cardType: CardType

    init(id: Int, name: String, cost: Int, description: String, cardType: CardType, target: Target, abilities: [Playable: Int]) {
        self.id = id
        self.name = name
        self.cost = cost
        self.description = description
        self.cardType = cardType
        self.abilities = abilities
        self.target = target
    }
}

The 6th variable in the class abilities throws an error when I try to put a protocol as the key. It gives me: Type 'Playable' does not conform to protocol 'Hashable'.

I want the abilities variable to have a key of an object that implements Playable and a value of Int, eg. abilities = [DrawACard: 5]

How can I go about doing that?

Error

3 个答案:

答案 0 :(得分:1)

This kind of mixing protocols with class inheritance is notoriously complicated. The answer to your specific issue is you can't directly, because if you make Playable conform to Hashable, you can't make an array of them.

The simplest solution is don't use a protocol here. Just make an abstract class. Swift isn't very good at abstract classes, but they will make most of these problems go away.

Given your specific case, I might also consider using an enum for Playable rather than a protocol. That would likely make things simpler.

From there, the solutions get a bit more complicated. For example, you could create a ClassDictionary along the lines of my ClassSet experiment. Or you can build a type-eraser. But it gets ugly.

You could also consider switching from a class to a struct, and getting rid of the Dictionary, and just using an Array. If the point of the Int is a count, then just have multiple copies of the struct. If the point of the Int is a parameter (like "how much damage"), then that should be inside the Damage struct, not in a dictionary.

(Unrelated note, your hash values are very strange. They'll work, but this isn't how hashes are meant to function.)

As an example of where I'm going with this, I'm seeing something like:

protocol Playable {
    func play(game: Game) -> Player
}

struct Damage: Playable {
    let amount: Int

    func play(game: Game) -> Player {
        // do stuff
        return game.activePlayer
    }
}

struct DrawACard: Playable {
    func play(game: Game) -> Player {
        // do stuff
        return game.activePlayer
    }
}

struct Card {
    let id: Int
    let name: String
    let cost: Int
    let description: String
    let cardType: CardType
    let target: Target
    let abilities: [Playable]
}

// A card that inflicts one damage
let card = Card(id: 1,
                name: "Strike",
                cost: 1,
                description: "Strike 'em",
                cardType: CardType(),
                target: Target(),
                abilities: [Damage(amount: 1)])

This switched everything to immutable structs on purpose; I believe everything you're describing here are really value types.

答案 1 :(得分:0)

I would change the definition of the classes to this, so the classes inherit from a Hashable base class:

class PlayableClass: Hashable {

    var hashValue: Int = 0

    static func ==(lhs: PlayableClass, rhs: PlayableClass) -> Bool {
        return true
    }
}

class Damage: PlayableClass, Playable {

    override init() {
        super.init()
        hashValue = 1
    }

   ...
}

class DrawACard: PlayableClass, Playable {

    override init() {
        super.init()
        hashValue = 2
    }

    ...
}

class Card {
    ...
    var abilities: [PlayableClass: Int]
    ...

    init(id: Int, name: String, cost: Int, description: String, abilities: [PlayableClass: Int]) {
        ...
    }
}

答案 2 :(得分:0)

这可能无法直接回答问题;但是,这可能对以后的读者有所帮助。

一个想法是使用AnyHashable并使用强制转换来获取/设置所需的内容。
定义字典:

var abilities: [AnyHashable: Int] = [:]

然后创建一些方法来添加/删除字典中的项目。

func addPlayable<T: Playable>(_ myPlayable: T) where T:Hashable {
    abilities[myPlayable] = 0
}

func removePlayable<T: Playable>(_ myPlayable: T) where T:Hashable {
    abilities.removeValue(forKey: myPlayable)
}

像这样访问元素:

func getValue<T: Playable>(_ obj: T) -> Int? where T:Hashable {
    return abilities[obj]
}

或者,如果要迭代字典,可以执行以下操作:

abilities.forEach { (it) in
    (it.key as? Playable)?.play(...)
}