完成块 Objective-C

Posted

技术标签:

【中文标题】完成块 Objective-C【英文标题】:Completion Blocks Objective-C 【发布时间】:2015-03-02 00:27:17 【问题描述】:

我正在尝试在 ios 中编写一个带有块的完成处理程序,但不确定它为什么不工作。

这是我目前所拥有的

typedef void(^myCompletion)(BOOL);

-(void) showAnswer 
   [self showAnswer:^(BOOL finished) 
       if (finished) 
         LnbScene *scene = (LnbScene *)gameScene.lnbScene;
         //once the tiles position have been updated then move to the next riddle
           [scene nextRiddle];
       
   ];


// This method should update the position of tiles on the scene
    -(void) showAnswer:(myCompletion) compblock
        LnbScene *scene = (LnbScene *)gameScene.lnbScene;
        NSArray *tilesArray = [scene.tilesBoundary children];

        for (Tile *tile in tilesArray) 
            if (tile.positionInAnswer != 17) 
                [tile removeFromParent];
                [scene.spacesBoundary addChild:tile];
                CGPoint targetPoint = CGPointMake(tile.targetPoint.x, tile.targetPoint.y + 6);
                tile.position = targetPoint;
            
        
        compblock(YES);
    

我正在从场景中调用 showAnswer 方法,如下所示:

     GameViewController *gVC = (GameViewController *)self.window.rootViewController;
     [gVC showAnswer];

当我逐步执行代码时,我没有遇到任何错误,并且序列按预期进行,即。磁贴位置发生更改,然后完成处理程序触发到 nextRiddle 方法。唯一的问题是没有更新任何界面。有什么我想念的吗?

我已经通过取出完成块测试了磁贴重新定位的代码是否可以工作,我在界面中查看它并获得所需的结果。所以我很确定问题在于我如何实现这些块。我以前从未写过一个块,所以我真的一头雾水。

-(void) showAnswer 
    LnbScene *scene = (LnbScene *)gameScene.lnbScene;
    NSArray *tilesArray = [scene.tilesBoundary children];

    for (Tile *tile in tilesArray) 
        if (tile.positionInAnswer != 17) 
            [tile removeFromParent];
            [scene.spacesBoundary addChild:tile];
            CGPoint targetPoint = CGPointMake(tile.targetPoint.x, tile.targetPoint.y + 6);
            tile.position = targetPoint;
        
    

【问题讨论】:

我不确定我是否理解问题所在。您说“磁贴位置已更改”,但界面未更新。正在改变的磁贴位置不是你想在界面中看到的吗? 对不起 - 我解释得不好。瓷砖不会在界面上更新。我的意思是当我单步执行代码并在调试器中检查它时,一切似乎都可以正常工作,但是当我运行并查看界面时,我看不到所需的结果。下一个谜语更改也不会在界面中生效。 我的猜测是,当您调用 showAnswer 方法时,您的 scene 对象为零。你的块代码很好。另一种可能性是您尝试在主线程之外更新 UI,但至少从您迄今为止发布的代码来看,它看起来不像。 【参考方案1】:

我认为这个问题似乎是一个概念上的错误——代码的“流” 在-(void) showAnswer:(myCompletion) comp block方法中真实地表示了UI元素在时间上的流动在表现层中。

但是在实践中并不是那么简单。 UI 层以 60 Hz 帧速率呈现。这意味着您每秒可以看到 60 个(帧)屏幕,并且每个屏幕的内容都需要提前计算、处理、展平、渲染。

这意味着 1 帧(屏幕)循环/通过持续大约。 16 毫秒。如果我没记错的话,作为一名程序员,你有 11 毫秒的时间来提供所有相关代码,以便将其处理成可视信息。这段代码被一次性处理,这是我们问题的答案

想象一下,如果你有一个方法会说


   //some other UI code...
   self.view.backgroundColor = [UIColor greenColor]; 
   self.view.backgroundColor = [UIColor yellowColor]; 
   //some other UI code...

现在假设我们在 1 个这样的 16 毫秒通道内。 这段代码将在任何渲染发生之前预先处理,结果背景颜色将为黄色。为什么?因为在循环准备阶段,此代码被处理并且框架将绿色解释为毫无意义,因为该值立即被黄色覆盖。 因此,渲染器的说明将仅包含黄色背景信息。

现在回到您的实际代码。我认为你所有的代码都被处理得足够快,可以适应那个 11 毫秒的窗口,问题是你并没有真正等待瓷砖的交换,因为有一个带有 YES 参数的块作为方法的一部分,并且一个已经滑动到接下来的任何事情。

因此,渲染确实得到了简单地继续下一件事的指令。而且它没有动画。

尝试将此“瓷砖交换”放入“动画”块中 [UIview animateWithDuration:....completion] 方法..并将您的块放入完成块中。是的,你会有一个街区,但那很好。

[UIView animateWithDuration:0.4f
                 animations:^
                                 //tile swapping code
                            
                 completion:^(BOOL finished)
                             
                                 complBlock(YES);
                             ];

我不完全确定它是否会起作用,但让我们试试吧。

【讨论】:

仍在消化以上所有内容。会回复你,看看它是否有效。 这个 WWDC 视频更详细地讨论了它。 developer.apple.com/videos/wwdc/2014/#419 我发现我的代码有问题 - 是 [scene nextRiddle] 方法。下一个 Riddle 方法实例化了一个新的 GameViewController。但我喜欢上面使用 UIView animateWithDuration 的解决方案。我认为它看起来会更聪明。所以谢谢你的帮助。我会看看那个视频【参考方案2】:

UI 相关方法应该在主线程上调用。你可以试试这个:

[self showAnswer:^(BOOL finished) 
   if (finished) 
     LnbScene *scene = (LnbScene *)gameScene.lnbScene;

     dispatch_async(dispatch_get_main_queue(), ^
          [scene nextRiddle];
     );
   
];

【讨论】:

以上是关于完成块 Objective-C的主要内容,如果未能解决你的问题,请参考以下文章

我的 animatewithduration,完成块只执行一次

在完成块完成之前返回的函数

访问变量输出完成块

如何快速使用完成块?

CATransaction 完成块永远不会触发

Swift - 从完成块中解除视图控制器