如何在玩家按下不同视图控制器中的按钮之前关闭?

How do I make a closure wait until the player pressed a button in a different view controller?

我正在 Swift 3 中编写国际象棋 GUI,并使用 nvzqz/Sage 作为国际象棋 model/library。现在我遇到了一个问题,一个用于 piece promotion 的 Sage 闭包。

Sage(在其游戏class中)使用execute(move: promotion:)方法来执行提升移动它有一个关闭 returns 一种促销品。这允许在选择促销件种类之前提示用户输入促销件或执行任何其他操作,如下所示:

try game.execute(move: move) {
    ...
    return .queen
}

我实现了一个升级视图控制器("pvc"),它在这个闭包中被调用,这样玩家就可以 select 新的棋子:

// This is in the main View Controller class

/// The piece selected in promotionViewController into which a pawn shall promote
var newPiece: Piece.Kind = ._queen // default value = Queen

try game.execute(move: move) {
    boardView.isUserInteractionEnabled = false

    // promotionview controller appears to select new promoted piece
    let pvc = PromotionViewController(nibName: nil, bundle: nil)
    pvc.delegate = self
    pvc.modalPresentationStyle = .overCurrentContext
    self.present(pvc, animated:true)

    return newPiece
}

当 pvc 中新片的按钮被按下时,pvc 将自行解散,selected 片的数据(常量 selectedType) 通过委托传回主视图控制器:

// This is in the sending PVC class
protocol PromotionViewControllerDelegate {
    func processPromotion(selectedType: Piece.Kind)
}

func buttonPressed(sender: UIButton) {
    let selectedType = bla bla bla ...
    delegate?.processPromotion(selectedType: selectedType)
    presentingViewController!.dismiss(animated:true)
}


// This is in the receiving main View Controller class
extension GameViewController: PromotionViewControllerDelegate {

func processPromotion(selectedType: Piece.Kind) {
    defer {
        boardView.isUserInteractionEnabled = true
    }
    newPiece = selectedType
}

我遇到的问题是闭包(在 game.execute 方法中)不会等到玩家在 pvc 中创建他的 selection (并立即 returns newPiece 变量,它仍然是默认值)这样我就再也不会处理除默认值以外的其他促销片了。

如何让关闭等到玩家按下 pvc 中的按钮?

当然,我试图找到解决方案并阅读有关回调、完成处理程序或 属性 观察者的信息。我不知道哪个是最好的前进方式,一些想法:

完成处理程序:pvc 在按下按钮事件时自行解除,因此完成处理程序不在接收(主)视图控制器中。我该如何处理?

属性观察者:我只能在设置提升块后调用try game.execute(move)方法(使用 didset) 但这会使代码难以阅读并且不能使用 game.execute 方法提供的漂亮闭包。

回调可能与完成处理程序有关,但我不确定。

因此您在 game.execute(move: move) 中的块将完全执行,这是 Sage API 设计的。你不能暂停它很容易,但它是可行的,还是让我们尝试用另一种方式解决它;

为什么要在这个块内调用view controller的presentation?一定要试着把它移开。调用 try game.execute(move: move) { 只能在 processPromotion 委托方法中调用。您没有 post 任何代码,但是无论此 try game.execute(move: move) { 代码在哪里,都需要单独显示一个视图控制器来替换它。

然后在委托上你甚至不需要保留值 newPiece = selectedType 而只是调用 try game.execute(move: move) { return selectedType }.

关于暂停块:

不可能直接 "pause" 一个块,因为它是执行的一部分,这意味着整个操作需要暂停,这最终意味着您需要暂停整个线程。这意味着您需要将调用移至单独的线程并暂停该线程。这仍然只有在 API 支持多线程的情况下才有效,如果回调在与其 execute 调用相同的线程上被调用......所以有很多工具和方法可以锁定线程所以让我只使用使线程休眠的最原始的方法:

    var executionLocked: Bool = false

    func foo() {
        DispatchQueue(label: "confiramtion queue").async {
            self.executionLocked = true
            game.execute(move: move) {
                // Assuming this is still on the "confiramtion queue" queue
                DispatchQueue.main.async {
                    // UI code needs to be executed on main thread
                    let pvc = PromotionViewController(nibName: nil, bundle: nil)
                    pvc.delegate = self
                    pvc.modalPresentationStyle = .overCurrentContext
                    self.present(pvc, animated:true)
                }
                while self.executionLocked {
                    Thread.sleep(forTimeInterval: 1.0/5.0) // Check 5 times per second if unlocked
                }
                return self.newPiece // Or whatever the code is
            }
        }
    }

现在在你的委托中你需要:

func processPromotion(selectedType: Piece.Kind) {
    defer {
        boardView.isUserInteractionEnabled = true
    }
    newPiece = selectedType
    self.executionLocked = false
}

所以这里发生的是我们开始一个新线程。然后锁定执行并在 game 实例上开始执行。在块中,我们现在在主线程上执行我们的代码,然后创建一个 "endless" 循环,其中一个线程每次都会休眠一点(实际上并不需要休眠,但它可以防止循环占用太多时间 CPU 力量)。现在所有的事情都发生在主线程上,即出现一个新的控制器,用户可以用它做一些事情......然后一旦完成,委托将解锁执行锁,这将使 "endless" 循环退出(在另一个线程)和 return 您的 game 实例的值。

我不希望您实施此操作,但如果您将确保采取所有预防措施以在需要时正确释放循环。就像如果视图控制器被关闭它应该解锁它,如果一个委托有一个 "cancel" 版本它应该退出...