Swift Tree 实现中的弱变量

问题描述 投票:0回答:1

我正在尝试在 Swift 中构建一个树实现来表示国际象棋游戏。

游戏由一系列动作组成,但给定棋盘位置的替代动作是有效的。我想在 GUI 中遍历树,这就是为什么我添加了方法来转到树中的特定节点。

但是我正在努力获得正确的内存模型。对于我的任务,我想保留对给定节点的下一个节点及其父节点的引用。根据我的理解,这些应该是“弱”的,以便不引入保留循环。然而,这样做时我的实现崩溃了(因为我猜我没有对给定节点的引用?)。 有人可以启发我如何更改现有的实现以使其正常工作吗?当我删除weak关键字时,我的测试成功了,但是我认为这是不对的,因为再次因为可能的保留周期。

我删除了一些实现以使其更具可读性:

import Foundation /// GameNode represents a node of a chess game tree public final class GameNode { // MARK: - Properties /// The position of the node public let position: Position /// Uniquely identifies a node public let nodeId: UUID /// Is the node at the top of the tree public let isTopNode: Bool /// The chess move that gets from the parent node to this one public let move: Move? /// An optional move annotation like !!, !?, ?? public let annotation: String? /// A comment for the move public let comment: String? /// The parent node public internal(set) weak var parent: GameNode? /// Pointer to the main variation public internal(set) weak var next: GameNode? /// Other possible variations from this node public internal(set) var variations: [GameNode] // MARK: - Init /// Creates a root node public init(position: Position = .initial, comment: String? = nil) { self.position = position self.nodeId = UUID() self.isTopNode = true self.move = nil self.annotation = nil self.parent = nil self.comment = comment self.next = nil self.variations = [] } /// Creates a node which is the result of making a move in another node public init(position: Position, move: Move, parent: GameNode, annotation: String? = nil, comment: String? = nil) { self.position = position self.nodeId = UUID() self.isTopNode = false self.move = move self.annotation = annotation self.parent = parent self.comment = comment self.next = nil self.variations = [] } /// Reconstructs the move sequence from the start of the game to this point public func reconstructMovesFromBeginning() -> [Move] { if parent?.isTopNode == true { return [move].compactMap({ $0 }) } var moves = parent?.reconstructMovesFromBeginning() ?? [] if let move { moves.append(move) } return moves } } public final class Game { public private(set) var current: GameNode public init(root: GameNode = GameNode()) { self.current = root } var root: GameNode? { var tmp: GameNode? = current while let currentTmp = tmp, !currentTmp.isTopNode { tmp = currentTmp.parent } return tmp } public var isAtEnd: Bool { current.next == nil } public func goBackward() { guard let parent = current.parent else { return } self.current = parent } public func go(to node: GameNode) { self.current = node } public func play(move: Move, comment: String? = nil, annotation: String? = nil) throws { let newPosition = try current.position.play(move: move) let newNode = GameNode(position: newPosition, move: move, parent: current, annotation: annotation, comment: comment) if !isAtEnd { current.next?.variations.append(newNode) } else { current.next = newNode } go(to: newNode) } public var uciPath: [String] { current.reconstructMovesFromBeginning().map(\.uci) } }

测试:

func testGameToUCIPath() throws { let game = try Game(fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") try game.play(move: .init(from: Squares.e2, to: Squares.e4)) try game.play(move: .init(from: Squares.e7, to: Squares.e5)) try game.play(move: .init(from: Squares.g1, to: Squares.f3)) try game.play(move: .init(from: Squares.b8, to: Squares.c6)) try game.play(move: .init(from: Squares.f1, to: Squares.b5)) XCTAssertEqual(game.uciPath, ["e2e4", "e7e5", "g1f3", "b8c6", "f1b5"]) game.goBackward() XCTAssertEqual(game.uciPath, ["e2e4", "e7e5", "g1f3", "b8c6"]) try game.play(move: .init(from: Squares.f1, to: Squares.c4)) XCTAssertEqual(game.uciPath, ["e2e4", "e7e5", "g1f3", "b8c6", "f1c4"]) }


swift automatic-ref-counting
1个回答
0
投票

当我们将该规则应用于您的代码时,

parent

将保持较弱,而

next
节点需要较强。
public final class GameNode {
    ...
    /// The parent node
    public internal(set) weak var parent: GameNode?
    
    /// Pointer to the main variation
    public internal(set) var next: GameNode?
}

但是,为了保持整个层次结构的活力,您还需要对初始 
root

节点保持强引用。否则,当您重新分配

current
节点时,其父节点将被销毁,因为没有强引用将其保持活动状态。
这意味着您将需要 

Game

类中的另一个字段

public final class Game {
    /// initial root node 
    public private(set) var root: GameNode

    public private(set) var current: GameNode
    
    public init(root: GameNode = GameNode()) {
        self.root = root
        self.current = root
    }

此外,
GameNode

类中的引用循环还存在另一个潜在问题。那是

variations
数组,它也保存对节点的强引用。如果您仅存储该数组中的后续节点(这就是您的代码注释所暗示的),那么它会很好,但如果您存储该特定节点或任何先前的节点,您将具有强引用循环。
    

© www.soinside.com 2019 - 2024. All rights reserved.