Home Should I be using Classes or Structs for elements stored in a Swift Array
Reply: 3

Should I be using Classes or Structs for elements stored in a Swift Array

MDMonty
1#
MDMonty Published in 2018-02-12 15:38:30Z

I would like to mutate a property in a Swift struct, stored within an array. I have done a reassignment dance, but that it doesn't feel right.

I'm encouraged to use Struct's where possible, however this relatively simple use case (below) is pushing me towards using Classes (Reference Types).

Should I be using Classes for Game and/or Player?

Please find below a code sample .. with accompanying UnitTest

Test Summary
• Create a Game
• Create two Players
• Add both Players to Game
• Send message to Game to decrementPlayer
• Game iterates over collection (players)
• Finds player and sends message decrementScore
Test Failed - Players' scores were not as expected (60 & 70 respectively)

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
    }
}
JeremyP
2#
JeremyP Reply to 2018-02-12 16:03:17Z

I'm encouraged to use Struct's where possible

Yes, that is problematic. You should be encouraged to use structs where appropriate. Generally speaking, I find that structs aren't always as appropriate as fashion dictates.

Your problem here is that the for var player ... statement actually makes a mutable copy of each player as it iterates and amends the copy. If you want to stick with structs, you'll probably need to adopt a more functional approach.

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

Or a more traditional (and almost certainly more efficient) way would be to find the index of the player you want

mutating func decrementPlayer(_ decrementPlayer: Player, byScore: Int) {
    if let index = players.index(of: decrementPlayer)
    {
        players[index].decrementScore(by: byScore)
    }
}
matt
3#
matt Reply to 2018-02-12 16:30:51Z

I would like to mutate a property in a Swift struct, stored within an array. I have done a reassignment dance, but that it doesn't feel right.

Well, that's what you would have to do, since structs are not mutable in place. If you want to be able to mutate the object in place (within the array), you do need a class.

Cristik
4#
Cristik Reply to 2018-02-17 07:53:46Z

The question that should be asked when deciding between value types (structs) and reference types (classes) is if makes sense to have duplicate instances.

For example take number 5, which is an Int. You can copy it as many times, it's cheap (as structs are), and doesn't pose problems if copied.

Now let's consider a FileHandle. Can it be a struct? Yes. Should it be a struct? No. Making it a struct means the handle will get copied whenever passed as argument or stored as property. Meaning everyone will have a different reference to that handle (intentionally ignoring copy-on-write). If one reference holder decides to close the handle, this indirectly invalidate all the other FileHandle references, which will likely trigger unexpected behaviour.

In your particular case, I'd say to make Player, a class. I assume the player will have some UI effects also, so it makes sense to be a reference type. However, if Player is used only for statistical purposes, then make it a struct.

You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.318701 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO