CADisplayLink 目标选择器在失效后被触发

发布于 11-16 09:11 字数 1135 浏览 5 评论 0原文

我有一个 CADisplayLink 可以触发 Director 对象中的 draw 方法。我想要使​​ CADisplayLink 无效,然后释放 Director 对象使用的一些单例 Cache 对象。 draw 方法不会保留单例 Cache 对象。

在Director中名为stopAnimation的方法中(该方法与draw方法无关),我这样做:

[displayLink invalidate];

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

这种情况只会在某些时候发生:有时应用程序不会崩溃,因为 Cache 对象在 displayLink 实际上失效且绘制方法已经完成运行后被释放。

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

我尝试了多种组合,包括使用

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

在主线程上执行 displayLink invalidate或尝试执行它在 currentRunLoop 中使用

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

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

我也不想使用具有任意延迟的 performSelector:withObject:afterDelay: 方法。我想确保 displayLink 无效,绘制方法结束,并且不会再次运行。

I have a CADisplayLink that triggers a draw method in a Director object. I want to invalidate the CADisplayLink and then deallocate some singleton Cache objects that are used by the Director object. The singleton Cache objects are not retained by the draw method.

In a method called stopAnimation in the Director (this method is unrelated to the draw method), I do:

[displayLink invalidate];

and then I start releasing the singleton Cache objects, but then the CADisplayLink fires and the draw method gets called one last time. The draw methods tries to access the deallocated singleton objects and everything crashes.

This only happens sometimes: there are times in which the app doesn't crash because the Cache objects are released after the displayLink is actually invalidated and the draw method has already finished running.

How can I check, after invalidating the displayLink, that the draw method has finished running and that it won't fire again, in order so safely invalidate the Cache objects? I don't want to modify the draw method if possible.

I tried a number of combinations, including performing displayLink invalidate on the main thread using

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

or trying to perform it in the currentRunLoop by using

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

but the results is always the same, sometimes it releases the shared Caches too early.

I also don't want to use the performSelector:withObject:afterDelay: method with an arbitrary delay. I want to make sure the displayLink is invalidated, that the draw method ended, and that it won't be run again.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

傲娇萝莉攻2024-11-23 09:11:12

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

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

也许最简单的解决方案是在您的并条机方法中放置一个标志和一个“if 语句”,

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

然后无论您在何处使显示链接无效,都将“schaduledForDestruction”设置为 YES。

如果您确实认为显示链接再次调用该方法,则可以在该方法内使用另一个“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];
}

This might be a bit late but since there has been no answers...

I do not think, your selector is called once more, but rather the display link's thread is in the middle of your draw frame method. In any case, the problem is quite the same.. This is multithreading and by trying to dealloc some objects in one thread while using them in another will usually result in a conflict.

Probably the easiest solution would be putting a flag and an "if statement" in your draw frame method as

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

and then wherever you are invalidating your display link set "schaduledForDestruction" to YES.

If you really think the display link calls tis method again, you could use another if inside that one "destructionInProgress".

If you do not want to change the draw frame method, you could try forcing a new selector to the display link...

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);
}

or just consider something like this (but I can't say this is safe. If the newly created display link can run the selector on a different thread then the original, it solves nothing)..

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];
}
遮了一弯2024-11-23 09:11:12

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

如果在运行循环正在执行处理程序例程的过程中触发计时器,则计时器会等到下一次通过运行循环时调用其处理程序例程。

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

class Layer: CALayer {

    var shouldDisplay: Bool = true

    override func display() {
        if shouldDisplay {
            super.display()
        }
    }
}

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

The problem is that CALayer's display() continues to be called even after the CADisplayLink is released from the run loop mode.

If a timer fires when the run loop is in the middle of executing a handler routine, the timer waits until the next time through the run loop to invoke its handler routine.

The most future-proof way to prevent the layer from updating after calling invalidate() is to subclass CALayer, add a flag, change the flag alongside invalidate(), and check the flag's value before calling super.display().

class Layer: CALayer {

    var shouldDisplay: Bool = true

    override func display() {
        if shouldDisplay {
            super.display()
        }
    }
}

So, in conjunction with invalidating your display link, set the layer's shouldDisplay to false. This will prevent the subclass from continuing to reload the contents of the layer, regardless of which thread calls invalidate().

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文