【问题标题】:CADisplayLink target selector being triggered after it is invalidatedCADisplayLink 目标选择器在失效后被触发
【发布时间】:2011-06-20 23:55:43
【问题描述】:

我有一个 CADisplayLink 触发了 Director 对象中的 draw 方法。我想使 CADisplayLink 无效,然后取消分配 Director 对象使用的一些单例缓存对象。 draw 方法不保留单例缓存对象。

在Director中一个叫stopAnimation的方法中(这个方法和draw方法无关),我这样做:

[displayLink invalidate];

然后我开始释放单例缓存对象,但随后 CADisplayLink 触发并且最后一次调用了 draw 方法。 draw 方法尝试访问已释放的单例对象,然后一切都崩溃了。

这种情况有时会发生:有时应用程序不会崩溃,因为在 displayLink 实际无效并且 draw 方法已经完成运行之后释放了 Cache 对象。

在使 displayLink 无效后,如何检查 draw 方法是否已完成运行并且不会再次触发,以便安全地使 Cache 对象无效?如果可能,我不想修改draw 方法。

我尝试了多种组合,包括使用在主线程上执行displayLink invalidate

[self performSelectorOnMainThread:@selector(stopAnimation) withObject:self waitUntilDone:YES]

或尝试在 currentRunLoop 中使用

[[NSRunLoop currentRunLoop] performSelector:@selector(stopAnimation) target:self argument:nil order:10 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];

但结果总是一样的,有时它会过早释放共享缓存。

我也不想随意延迟使用performSelector:withObject:afterDelay: 方法。我想确保 displayLink 无效,draw 方法结束,并且不会再次运行。

【问题讨论】:

    标签: ios cocos2d-iphone cadisplaylink


    【解决方案1】:

    这可能有点晚了,但由于没有答案......

    我不认为,您的选择器再次被调用,而是显示链接的线程位于您的并条框方法的中间。无论如何,问题都是一样的。这是多线程,在一个线程中释放一些对象,而在另一个线程中使用它们通常会导致冲突。

    可能最简单的解决方案是在您的并条框方法中放置一个标志和一个“if 语句”

    if(schaduledForDestruction) {
    [self destroy];
     return;
    }
    

    然后无论您在哪里使显示链接无效,都将“schaduledForDestruction”设置为“是”。

    如果你真的认为显示链接再次调用了 tis 方法,你可以在那个“destructionInProgress”中使用另一个。

    如果您不想更改并条方法,您可以尝试强制一个新的选择器到显示链接...

    CADisplayLink *myDisplayLink;
    BOOL resourcesLoaded;
    SEL drawSelector;
    
    - (void)destroy {    
        if(resourcesLoaded) {
            [myDisplayLink invalidate];
            //free resources
            resourcesLoaded = NO;
        }    
    }
    - (void)metaLevelDraw {
        [self performSelector:drawSelector];
    }
    - (void)drawFrame {
        //draw stuff
    }
    - (void)beginAnimationing {
        drawSelector = @selector(drawFrame);
        myDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(metaLevelDraw)];
        [myDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    }
    - (void)endAnimationing {
        drawSelector = @selector(destroy);
    }
    

    或者只是考虑这样的事情(但我不能说这是安全的。如果新创建的显示链接可以在与原来不同的线程上运行选择器,那么它什么也解决不了)..

    CADisplayLink *myDisplayLink;
    BOOL resourcesLoaded;
    
    - (void)destroy {    
        if(resourcesLoaded) {
            [myDisplayLink invalidate];
            //free resources
            resourcesLoaded = NO;
        }    
    }
    - (void)drawFrame {
        //draw stuff
    }
    - (void)beginAnimationing {
        myDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];
        [myDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    }
    - (void)endAnimationing {
        [myDisplayLink invalidate];
        myDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(destroy)];
        [myDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    }
    

    【讨论】:

    • 有用信息永远不会迟到!这是一个很好的答案,这些信息真的很有帮助。我还没有做一些测试,但可能你对导致问题的线程是正确的(而不是在 CADisplayLink 结束后再次调用 draw 方法)。我还没有尝试过你的解决方案,但我认为第二个看起来很优雅。非常感谢您花时间回答!
    • 不幸的是,这两种方法在 iOS 11 中都不适合我。尚未在 iOS 10 中测试。
    • @chicobermuda 我对 iOS 11 上的显示链接没有任何特殊问题,因此您的问题可能有点独特。您有任何其他信息或代码要显示吗?也许问了另一个问题?
    • 我在 Swift 中尝试了这两种方法,发现设置和操作选择器并不能阻止层的 drawdisplay 方法在从运行循环模式释放后触发。来自 Apple:“如果在 run loop 正在执行处理程序例程的中间触发计时器,则计时器将等待直到下一次通过 run loop 调用其处理程序例程。”您的第一个解决方案更安全,尽管它应该从图层的子类中调用,因为我们知道不必调用超类的选择器来重绘图层。
    【解决方案2】:

    问题是CALayerdisplay() 即使在CADisplayLink 从运行循环模式中释放后仍会继续被调用。

    如果在 run loop 正在执行处理程序例程时触发计时器,则计时器将等待直到下一次通过 run loop 调用其处理程序例程。

    在调用invalidate() 后防止层更新的最面向未来的方法是继承CALayer,添加一个标志,在invalidate() 旁边更改标志,并在调用super.display() 之前检查标志的值。

    class Layer: CALayer {
    
        var shouldDisplay: Bool = true
    
        override func display() {
            if shouldDisplay {
                super.display()
            }
        }
    }
    

    因此,在使显示链接无效的同时,将图层的shouldDisplay 设置为 false。这将阻止子类继续重新加载层的内容,无论哪个线程调用invalidate()

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-19
      • 1970-01-01
      相关资源
      最近更新 更多