我应该为存储在Swift数组中的元素使用Classes或Structs

时间:2018-02-12 15:38:30

标签: swift class struct mutate

我想改变存储在数组中的Swift结构中的属性。 我做了一次重新分配的舞蹈,但感觉不对。

我鼓励尽可能使用Struct,但是这个相对简单的用例(下面)正在推动我使用类(参考类型)。

我应该Classes使用Game和/或Player吗?

请在下面找到代码示例..附带UnitTest

测试摘要
•创建游戏
•创建两个玩家
•将两个玩家添加到游戏中 •向Game发送消息以减少人数 •游戏迭代收集(players
•查找玩家并发送消息减量分数 •测试失败 - 玩家'分数不如预期的那样(分别为60和70)

struct Game {
    fileprivate(set) var players = [Player]()
}

extension Game {
    mutating func addPlayer(_ player: Player) {
        players.append(player)
    }

    mutating func decrementPlayer(_ decrementPlayer: Player, byScore: Int) {
        for var player in players {
            if player == decrementPlayer {
                player.decrementScore(by: byScore)
            }
        }
    }
}


struct Player {
    var name: String
    var score: Int

    init(name: String, score: Int) {
        self.name = name
        self.score = score
    }

    mutating func decrementScore(by byScore: Int) {
        self.score -= byScore
    }
}

extension Player: Equatable {
    public static func ==(lhs: Player, rhs: Player) -> Bool {
        return lhs.name == rhs.name
    }
}
class GameTests: XCTestCase {

var sut: Game!

func testDecrementingPlayerScores_isReflectedCorrectlyInGamePlayers() {
        sut = Game()
        let player1 = Player(name: "Ross", score: 100)
        let player2 = Player(name: "Mary", score: 100)

        sut.addPlayer(player1)
        sut.addPlayer(player2)
        XCTAssertEqual(2, sut.players.count)    // Passes

        sut.decrementPlayer(player1, byScore: 40)
        sut.decrementPlayer(player2, byScore: 30)
        XCTAssertEqual(60, sut.players[0].score) // Fails - score is 100 .. expecting 60
        XCTAssertEqual(70, sut.players[1].score) // Fails - score is 100 .. expecting 70
    }
}

3 个答案:

答案 0 :(得分:3)

  

我鼓励尽可能使用Struct

是的,这是有问题的。建议您使用struct s 相应的。一般来说,我发现struct并不总是与时尚指示一样合适。

你的问题是for var player ...语句实际上是每个玩家的可变副本,因为它会迭代并修改副本。如果你想坚持struct,你可能需要采用更实用的方法。

mutating func decrementPlayer(_ decrementPlayer: Player, byScore: Int) {
    players = players.map {
        return $0 == decrementPlayer ? $0.scoreDecrementedBy(by: byScore) : $0
    }
}

或者更传统(并且几乎肯定更有效)的方式是找到你想要的玩家的索引

mutating func decrementPlayer(_ decrementPlayer: Player, byScore: Int) {
    if let index = players.index(of: decrementPlayer)
    {
        players[index].decrementScore(by: byScore)
    }
}

答案 1 :(得分:0)

  

我想改变存储在数组中的Swift结构中的属性。我做了一次重新分配的舞蹈,但感觉不对。

嗯,这就是你必须要做的事情,因为结构不是可变的。如果你想能够在适当的位置(在数组中)改变对象,你需要一个类。

答案 2 :(得分:0)

在决定值类型(结构)和引用类型(类)之间应该问的问题是否有意义才能有重复的实例。

例如,取5号,这是一个Int。你可以多次复制它,它很便宜(如结构一样),如果复制也不会造成问题。

现在让我们考虑FileHandle。它可以是一个结构吗?是。它应该是一个结构吗?不能。使它成为一个结构意味着只要作为参数传递或存储为属性,句柄就会被复制。意味着每个人都会对该句柄有不同的引用(故意忽略copy-on-write)。如果一个引用持有者决定关闭句柄,则会间接使所有其他FileHandle引用无效,这可能会触发意外行为。

在你的特定情况下,我要说Player,一个班级。我假设玩家也会有一些UI效果,所以成为一个参考类型是有道理的。但是,如果Player仅用于统计目的,则将其设为结构。