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

# 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#
 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) } } 
 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.