Я пытаюсь использовать swift NSCopy, чтобы сделать глубокую копию объекта GKGameModel, включая всех игроков и ссылку на их кошелек (содержащий целые числа, представляющие их денежные средства).
Используя игровые площадки Swift, я пытаюсь перечислить все скопированныеигроки с $ 100, но оставляют оригинальные объекты игрока нетронутыми.
Однако я заметил, что код воздействует и на оригинальные объекты игрока.
На высоком уровне код должен показать:
Game class, a GKGameModel
Player class, a GKPlayerModel
Wallet class, handles very basic player's Int transactions.
Цели:
- Копирование класса игры, всех игроков и связанного с ними класса кошелька
- Начисление скопированных игроков на $ 100.
- Первоначальные игрокидолжно по-прежнему иметь $ 0
Код выглядит следующим образом:
// Uses Swift playground
import GameplayKit
class Game : NSObject, GKGameModel{
override var description: String {
return ("game: \(String(describing: self.players))")
}
// -- GKGameModel code follows --
var players: [GKGameModelPlayer]?
var activePlayer: GKGameModelPlayer?
func gameModelUpdates(for player: GKGameModelPlayer) -> [GKGameModelUpdate]? {
return nil
}
func unapplyGameModelUpdate(_ gameModelUpdate: GKGameModelUpdate) {
}
func apply(_ gameModelUpdate: GKGameModelUpdate) {
}
func setGameModel(_ gameModel: GKGameModel) {
guard let inputModel = gameModel as? Game else {
assertionFailure("No game model initialised")
return
}
guard let players = inputModel.players else {
assertionFailure("No players initialised")
return
}
self.activePlayer = inputModel.activePlayer
self.players = players
}
func copy(with zone: NSZone? = nil) -> Any {
print ("copying game obj!")
let copy = Game()
copy.setGameModel(self)
copy.players = self.players // if I do not include it, copy.players is nil
return copy
}
}
class Player : NSObject, NSCopying, GKGameModelPlayer {
override var description: String {
return ("name: \(name), cash: $\(cash), wallet: $\(wallet.balance)")
}
internal var playerId: Int = 0 {
didSet {
print ("Set playerId = \(self.playerId)")
}
}
var name: String
var cash : Int = 0
var wallet : Wallet = Wallet()
init(name: String) {
self.name = name
}
func copy(with zone: NSZone? = nil) -> Any {
print ("copying player!!") // this code is never reached
let copy = self
copy.wallet = self.wallet.copy(with: zone) as! Wallet
return copy
}
}
enum WalletError : Error {
case mustBePositive
case notEnoughFunds
}
fileprivate protocol WalletDelegate {
var balance : Int { get }
func credit(amount: Int) throws
func debit(amount: Int) throws
}
class Wallet : NSCopying, CustomStringConvertible, WalletDelegate {
public private(set) var balance: Int = 0
func credit(amount: Int = 0) throws {
try canCredit(amount: amount)
self.balance += amount
}
func debit(amount: Int = 0) throws {
try canDebit(amount: amount)
self.balance -= amount
}
func copy(with zone: NSZone? = nil) -> Any {
print ("copy wallet") // this code is never reached
let copy = Wallet()
return copy
}
}
extension Wallet {
private func canCredit(amount: Int) throws {
guard amount > 0 else {
throw WalletError.mustBePositive
}
}
private func canDebit(amount: Int) throws {
guard amount > 0 else {
throw WalletError.mustBePositive
}
guard self.balance >= amount else {
throw WalletError.notEnoughFunds
}
guard (self.balance - amount >= 0) else {
throw WalletError.notEnoughFunds
}
}
}
extension Wallet {
var description: String {
return ("Balance: $\(self.balance)")
}
}
let players : [GKGameModelPlayer] = [ Player.init(name: "Bob"), Player.init(name: "Alex"), Player.init(name: "John") ]
let game = Game()
game.players = players
func copyTheGame() {
let copiedGame = game.copy() as! Game
print ("BEFORE:")
print ("Original: \(String(describing: game))")
print ("Copied: \(String(describing: copiedGame))")
print ("----")
if let copiedPlayers = copiedGame.players {
for p in copiedPlayers as! [Player] {
do {
p.cash = 100 // try to manipulate a class variable.
try p.wallet.credit(amount: 100)
} catch let err {
print (err.localizedDescription)
break
}
}
}
print ("AFTER:")
print ("Original: \(String(describing: game))")
print ("Copied: \(String(describing: copiedGame))")
print ("----")
}
copyTheGame()
В моем выводе я получаю следующее:
copying game obj!
BEFORE:
Original: game: Optional([name: Bob, cash: $0, wallet: $0, name: Alex, cash: $0, wallet: $0, name: John, cash: $0, wallet: $0])
Copied: game: Optional([name: Bob, cash: $0, wallet: $0, name: Alex, cash: $0, wallet: $0, name: John, cash: $0, wallet: $0])
----
AFTER:
Original: game: Optional([name: Bob, cash: $100, wallet: $100, name: Alex, cash: $100, wallet: $100, name: John, cash: $100, wallet: $100])
Copied: game: Optional([name: Bob, cash: $100, wallet: $100, name: Alex, cash: $100, wallet: $100, name: John, cash: $100, wallet: $100])
----
Проблемы:
- Код копирования игрока никогда не попадет
- Код копирования кошелька никогда не ударится
- Зачисленные 100 долларов влияют как на оригинал, так и на копию.
Какя гарантирую, что любые изменения, которые я делаю, относятсяскопированный игровой объект, а не оригиналы?
С благодарностью
Обновление:
Мне удалось принудительно скопировать копию, перебрав всех игроковобъекты, но я не уверен, что это лучший способ сделать это.
Если я изменю функцию копирования в классе игры на:
func copy(with zone: NSZone? = nil) -> Any {
let copy = Game()
let duplicate = self.players?.map({ (player: GKGameModelPlayer) -> GKGameModelPlayer in
let person = player as! Player
let copiedPlayer = person.copy(with: zone) as! Player
return copiedPlayer
})
copy.players = duplicate
return copy
}
Это позволит мне сделать копиюиз всех объектов игрока, также;функция copy () в классе игрока и кошелька получила удар.