NSCopy GKGameModel неправильно копирует объекты игрока - PullRequest
0 голосов
/ 28 сентября 2018

Я пытаюсь использовать 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 () в классе игрока и кошелька получила удар.

1 Ответ

0 голосов
/ 01 октября 2018

Мне удалось решить проблему путем принудительного копирования ссылочных объектов, массивов или дочерних объектов

, то есть:

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